AI віртуальний фон (заміна фону) для відеозвонків
Стандартна реалізація віртуального фону через WebRTC та сторонній сервіс працює, поки мережа стабільна. На мобільному при деградації 4G час туру для відправки кадру на сервер і отримання маскованого результату зростає до 150–300 мс, викликаючи артефакти в реальному часі. Правильний підхід — on-device сегментація.
Чому серверна сегментація не працює на мобільному
Задача — виділити силует людини на кожному кадрі відеопотоку (30 fps), застосувати фон та повернути його у конвеєр перед кодуванням. Це означає ~33 мс бюджету на кадр включно з захопленням, інференцем моделі, постобробкою та рендерингом.
Серверний підхід: захопити → відправити → інференц → відповідь → рендерити. Навіть при ідеальній мережі, час туру додає 40–80 мс. На практиці—рух контуру, «привид» при русі.
На пристрої: захопити → інференц → рендерити. Все в одному конвеєрі.
iOS: MLKit + CoreImage або Vision
На iOS використовуйте фреймворк Vision з VNGeneratePersonSegmentationRequest. Apple додала його в iOS 15—працює на Neural Engine без явної загрузки моделі. Точність хороша для фронтальної камери, але видає розривистий контур зі складними зачісками та прозорою одежею.
// Налаштування сегментації
let request = VNGeneratePersonSegmentationRequest()
request.qualityLevel = .balanced // .accurate кращий контур, важче
request.outputPixelFormat = kCVPixelFormatType_OneComponent8
// У обробнику кадрів AVFoundation
let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer, options: [:])
try handler.perform([request])
guard let mask = request.results?.first?.pixelBuffer else { return }
// маска—8-bit CVPixelBuffer, застосуйте через CIBlendWithMask
CIBlendWithMask з CIContext(options: [.workingColorSpace: NSNull()]) рендерить на Metal, уникаючи конвертації кольорового простору. Без цього кожен кадр додає ~5 мс лише на конвертацію.
Для кращої сегментації конвертуйте TFLite DeepLab v3 або MediaPipe SelfieSegmentation у Core ML через coremltools та загрузьте через MLModel. MediaPipe дає стійкий контур навіть з м'якими краями.
Android: MLKit Selfie Segmentation
val segmenter = Segmentation.getClient(
SelfieSegmenterOptions.Builder()
.setDetectorMode(SelfieSegmenterOptions.STREAM_MODE) // оптимізовано для відео
.enableRawSizeMask()
.build()
)
// У обробнику CameraX ImageAnalysis
override fun analyze(imageProxy: ImageProxy) {
val inputImage = InputImage.fromMediaImage(imageProxy.image!!, imageProxy.imageInfo.rotationDegrees)
segmenter.process(inputImage)
.addOnSuccessListener { segmentationMask ->
val mask = segmentationMask.buffer
// Застосуйте фон через RenderScript або Vulkan compute shader
applyBackground(mask, imageProxy)
}
.addOnCompleteListener { imageProxy.close() }
}
STREAM_MODE критична—зберігає внутрішній стан між кадрами та працює швидше за SINGLE_IMAGE_MODE. На Pixel 6 з Tensor G2, інференц займає 8–12 мс. На бюджетних телефонах (Snapdragon 695)—20–28 мс. Для постобробки маски—RenderScript (застарілий API 31+) або Vulkan compute shader через RenderEffect на Android 12+.
Застосування фонів: три варіанти
Статичне зображення — найпростіший випадок. CIBlendWithMask на iOS, PorterDuff compositing на Android.
Розмиття (blur) — фільтр CIGaussianBlur з радіусом 12–20 застосовується до вихідного кадру, потім маска вибирає між оригіналом та розмитим. На Android—RenderEffect.createBlurEffect (API 31+) або користувацький blur через Vulkan.
Відеофон — потрібен декодер, синхронізований з часовою шкалою відеозвонку. На iOS—AVPlayerItemVideoOutput + Metal текстура. Важко по пам'яті: буфер відеофону + буфер камери + буфер маски + результат. На iPhone 12 з 4 GB добре, iPhone SE 2nd gen (3 GB) потребує агресивного повторного використання буфера.
Інтеграція у WebRTC конвеєр
Більшість мобільних рішень для вирахування будуються на WebRTC—через LiveKit, Daily.co, Agora або нативний WebRTC. Всі забезпечують механізм користувацького VideoSource/VideoProcessor для заміни кадрів перед кодуванням.
У LiveKit SDK для iOS це протокол VideoProcessor:
class BackgroundReplacementProcessor: VideoProcessor {
func process(frame: RTCVideoFrame) -> RTCVideoFrame? {
// Сегментація + застосування фону
// Повертаємо новий RTCVideoFrame з оброблювачем буфером
}
}
room.localParticipant?.videoTracks.first?.processor = BackgroundReplacementProcessor()
Важливо: RTCVideoFrame працює у CVPixelBuffer з форматом kCVPixelFormatType_420YpCbCr8BiPlanarFullRange. Конвертація в RGB для ML-інференцу та назад—втратна. Якщо модель приймає YUV—залиште формат нетронутим.
Оцінка та процес
Почніть з аудиту існуючого WebRTC стеку: який SDK, як організований конвеєр кадрів, список цільових пристроїв. Потім прототип з Vision/MLKit, вимірювання на реальних пристроях зі списку мінімальних вимог.
Критичні етапи: налаштування моделі сегментації за якістю/швидкістю, оптимізація постобробки маски (antialiasing контуру, feathering країв), тестування граничних випадків—неівномірне освітлення, складний фон, швидкі рухи.
Кошторис за часом
Базова реалізація з розмитим фоном (одна платформа) займає 2–3 тижні. Повна реалізація з підтримкою статичних зображень та відеофонів, обох платформ, інтеграція у існуючий WebRTC стек вимагає 5–8 тижнів.







