Реализация AI-анализа эмоций собеседника во время видеозвонка
Анализ эмоций через камеру в реальном времени — технически реализуемо, но требует особого подхода к этике и UX. Покажем лицо технической стороны, не скрывая ограничений: модели анализа эмоций — один из самых критикуемых с точки зрения достоверности инструментов в AI.
Важное ограничение, которое нельзя игнорировать
Академический консенсус (Lisa Feldman Barrett, 2019) и практика показывают: мимика не однозначно отображается на эмоцию. Один и тот же паттерн движения лицевых мышц означает разное для разных людей и культур. Поэтому:
- Называть вывод «эмоцией» некорректно — правильнее «аффективное состояние», «выражение лица»
- Системы ни в коем случае не должны использоваться для принятия кадровых или юридических решений
- Пользователь должен дать явное согласие на анализ своего лица
Это не просто этическое замечание — это архитектурное требование.
Технический стек
Детекция лица — MediaPipe Face Detection (iOS/Android), Vision VNDetectFaceRectanglesRequest (iOS).
Распознавание выражений — несколько вариантов:
- Apple Vision
VNDetectFaceExpressionsRequest(iOS 17+) — встроенный, без облака, 7 базовых Action Units - Microsoft Azure Face API — облачный, детальный, включает Action Units
- AWS Rekognition (DetectFaces) — облачный, 7 базовых эмоций
- FER+ модель (TFLite/CoreML) — open source, 8 классов, on-device
Для видеозвонков on-device — обязательно: нельзя стримить лицо собеседника в облако без его явного согласия.
Реализация на iOS с Vision (on-device)
// iOS 17+: анализ выражений лица через Vision
class FaceExpressionAnalyzer {
func analyze(sampleBuffer: CMSampleBuffer) async throws -> ExpressionResult? {
guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return nil }
let faceRequest = VNDetectFaceLandmarksRequest()
// iOS 17: анализ выражений — brow action units и т.д.
let expressionRequest = VNDetectFaceExpressionsRequest()
let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer)
try handler.perform([faceRequest, expressionRequest])
guard let faceObs = faceRequest.results?.first as? VNFaceObservation,
let exprObs = expressionRequest.results?.first as? VNFaceExpressionObservation else {
return nil
}
return ExpressionResult(
faceBox: faceObs.boundingBox,
browLower: exprObs.browLowerQuirk,
browRaise: exprObs.browRaiseRight + exprObs.browRaiseLeft,
eyesClosed: exprObs.eyeBlinkLeft + exprObs.eyeBlinkRight,
mouthSmile: exprObs.mouthSmileLeft + exprObs.mouthSmileRight,
mouthFrown: exprObs.mouthFrownLeft + exprObs.mouthFrownRight,
mouthOpen: exprObs.mouthOpen,
jawOpen: exprObs.jawOpen
)
}
}
VNDetectFaceExpressionsRequest работает с Action Units — базовыми движениями лицевых мышц из FACS (Facial Action Coding System). Это корректнее, чем «улыбка = счастье»: конкретное действие мышцы, без интерпретации.
Агрегация по времени
Один кадр — шум. Нужна агрегация по скользящему окну:
class ExpressionAggregator {
private var history: [ExpressionResult] = []
private let windowSize = 15 // ~0.5 сек при 30fps
func update(_ result: ExpressionResult) -> AggregatedExpression {
history.append(result)
if history.count > windowSize { history.removeFirst() }
return AggregatedExpression(
averageSmile: history.map { $0.mouthSmile }.average(),
averageBrowRaise: history.map { $0.browRaise }.average(),
averageJawOpen: history.map { $0.jawOpen }.average(),
// Тренд: растёт или падает улыбка за последние N кадров
smileTrend: computeTrend(history.map { $0.mouthSmile })
)
}
}
Интеграция в видеозвонок
Анализ выполняется на локальном видеопотоке с камеры, не на стриме собеседника. Стрим собеседника — другое устройство, доступа к его сырым кадрам через стандартный WebRTC нет. Есть два пути:
SDK с поддержкой analysis — Agora Video SDK позволяет подключить local video processor:
// Agora: обработка локального видео перед отправкой
class EmotionVideoProcessor: AgoraVideoFrameDelegate {
func onCapture(_ videoFrame: AgoraOutputVideoFrame,
sourceType: AgoraVideoSourceType) -> Bool {
// Анализируем свой кадр до отправки
if let pixelBuffer = videoFrame.pixelBuffer {
Task {
let result = try? await expressionAnalyzer.analyze(buffer: pixelBuffer)
// result — это анализ своих эмоций, не собеседника
await MainActor.run {
emotionDelegate?.didUpdateExpression(result)
}
}
}
return true // пропускаем кадр дальше в стрим без изменений
}
}
Peer-to-peer analysis — оба участника анализируют свои собственные выражения и передают результаты (не видео) через data channel. WebRTC data channel для JSON-пакетов — минимальный overhead.
// Передача данных об эмоциях через WebRTC DataChannel
struct EmotionDataPacket: Codable {
let timestamp: Double
let smile: Float
let browRaise: Float
let eyesClosed: Float
// НЕ передаём изображение — только числа
}
func sendEmotionData(_ expression: AggregatedExpression) {
let packet = EmotionDataPacket(
timestamp: Date().timeIntervalSince1970,
smile: expression.averageSmile,
browRaise: expression.averageBrowRaise,
eyesClosed: expression.averageJawOpen
)
let data = try! JSONEncoder().encode(packet)
dataChannel.sendData(RTCDataBuffer(data: data, isBinary: false))
}
Так каждый участник анализирует только себя, но видит агрегированные данные от собеседника. Приватно и технически чисто.
UX: как показывать результат
Показывать «злой / грустный / счастливый» — некорректно и потенциально обидно. Правильные варианты:
- Engagement indicator: «собеседник активно участвует в разговоре» (по browRaise + eyeBlink ритму)
- Attention level: нейтральный индикатор вовлечённости без интерпретации эмоции
- Настроение разговора: агрегация обоих участников в единый «тепловой» показатель
@Composable
fun EngagementIndicator(score: Float) {
Box(
modifier = Modifier
.size(12.dp)
.clip(CircleShape)
.background(
when {
score > 0.7f -> Color(0xFF4CAF50) // активен
score > 0.4f -> Color(0xFFFFC107) // нейтрален
else -> Color(0xFF9E9E9E) // пассивен
}
)
)
}
Никаких «лиц с эмоциями», никаких словесных ярлыков — только нейтральный цветовой индикатор.
Ориентиры по срокам
On-device анализ выражений через Vision + базовый engagement indicator в существующем видеозвонке — 1–2 недели. Полная система с peer-to-peer передачей данных через data channel, агрегацией, аналитикой разговора, экраном согласия и поддержкой iOS + Android — 2–4 недели.







