Blog

Minha câmera virtual com blur e auto-framing: um NVIDIA Broadcast só para a câmera

Eu usava o NVIDIA Broadcast só para uma coisa: desfocar o fundo da minha webcam. Só que ele insistia em ser muito mais do que eu pedia. Ativava efeito de ruído no microfone e nos alto-falantes quando eu só queria mexer na câmera, e ao ligar o PC abria a janela cheia na minha cara em vez de iniciar quieto na bandeja. Decidi resolver isso construindo minha própria ferramenta: um app de desktop que aplica blur de fundo e auto-framing apenas na câmera, expõe o resultado como uma webcam virtual para qualquer programa de chamada e inicia minimizado. Este artigo conta a decisão de arquitetura, o porquê de cada escolha técnica e como o app foi montado e validado.

2026-06-22 / Visão Computacional / 12 min

01

O problema com o NVIDIA Broadcast

O NVIDIA Broadcast é uma ótima suíte, mas para o meu uso ele resolvia demais. Três incômodos concretos me empurraram para construir algo próprio.

  • Escopo amplo demais: eu só queria blur na câmera, e o app girava em torno de três dispositivos (câmera, microfone e alto-falantes). Cada vez que eu mexia numa coisa, tinha o risco de ativar efeito onde eu não queria.
  • Abria maximizado no boot: ao ligar o PC, a janela do Broadcast aparecia inteira na tela, em vez de iniciar minimizada na bandeja. Todo dia eu fechava na mão.
  • Peso e acoplamento: uma suíte grande para uma função pequena. Eu queria uma ferramenta enxuta que fizesse só o blur e o enquadramento, do meu jeito.

A inspiração veio de um projeto web open source do Takashi Yoshinaga que faz blur de fundo e auto-framing com MediaPipe rodando no navegador. A ideia era exatamente o que eu queria, mas em página web não dá para virar webcam dos outros apps. Eu precisava de um aplicativo instalável no PC que expusesse uma câmera virtual.

02

Escolhendo a linguagem e a arquitetura

O requisito que define tudo não é o blur em si: é fazer o vídeo processado aparecer como uma webcam selecionável no Zoom, Meet, Discord e OBS. Isso exige registrar uma câmera virtual no Windows. Avaliei três caminhos.

AbordagemProsContras
Electron + JS (igual ao repo original)Reaproveita o MediaPipe.js do projeto web; UI fácilCriar câmera virtual real no Windows a partir do Electron é instável; bom só para janela de preview
C# / .NET nativoApp nativo robusto, instalador limpo, câmera virtual via DirectShowMediaPipe em C# é menos direto; desenvolvimento bem mais longo
Python + pyvirtualcamMediaPipe oficial, OpenCV maduro, câmera virtual pronta via backend do OBS, vira .exe com PyInstallerDepende do OBS instalado uma vez para registrar o driver da câmera virtual

Escolhi Python. O MediaPipe tem suporte oficial em Python, o OpenCV resolve captura e processamento de imagem com folga, e a biblioteca pyvirtualcam entrega a câmera virtual reaproveitando o driver que o OBS Studio já instala. É o melhor custo-benefício: chego rápido num app funcional e ainda empacoto tudo num executável.

03

Como o blur de fundo funciona

O blur depende de separar a pessoa do fundo. Para isso uso o Image Segmenter do MediaPipe com o modelo de selfie segmentation, que devolve, para cada pixel, a confiança de pertencer à pessoa. Com essa máscara, componho a pessoa nítida sobre uma cópia desfocada do mesmo frame.

import cv2
import numpy as np
import mediapipe as mp
from mediapipe.tasks import python as mp_python
from mediapipe.tasks.python import vision

options = vision.ImageSegmenterOptions(
    base_options=mp_python.BaseOptions(model_asset_path="selfie_segmenter.tflite"),
    running_mode=vision.RunningMode.VIDEO,
    output_confidence_masks=True,
)
segmenter = vision.ImageSegmenter.create_from_options(options)

def blur_background(frame_bgr, ts_ms, strength=25, threshold=0.5):
    rgb = cv2.cvtColor(frame_bgr, cv2.COLOR_BGR2RGB)
    image = mp.Image(image_format=mp.ImageFormat.SRGB, data=rgb)
    result = segmenter.segment_for_video(image, ts_ms)

    confidence = result.confidence_masks[0].numpy_view()  # [0..1] por pixel
    mask = (confidence >= threshold).astype(np.float32)
    mask = cv2.GaussianBlur(mask, (7, 7), 0)              # suaviza a borda

    k = strength | 1                                      # kernel impar
    blurred = cv2.GaussianBlur(frame_bgr, (k, k), 0)
    alpha = mask[:, :, None]
    out = frame_bgr * alpha + blurred * (1.0 - alpha)     # composicao alpha
    return out.astype(np.uint8)

Dois cuidados fazem diferença na qualidade: binarizar a máscara num limiar e depois suavizar a borda com um leve blur gaussiano, para a transição entre pessoa e fundo não ficar serrilhada; e usar o modo VIDEO do segmenter passando o timestamp de cada frame, o que dá rastreamento temporal e estabiliza o resultado quadro a quadro.

04

Como o auto-framing funciona

O auto-framing mantém seu rosto sempre bem enquadrado, como se a câmera te seguisse. Detecto o rosto com o Face Detector do MediaPipe (BlazeFace), calculo um retângulo de corte centrado nele com um zoom configurável e redimensiono esse corte de volta ao tamanho de saída. O segredo para não tremer é suavizar o movimento com média exponencial, a mesma ideia do projeto original.

def smooth(prev, target, factor=0.9):
    # factor alto = movimento mais suave e mais lento a reagir.
    if prev is None:
        return target
    return tuple(factor * p + (1 - factor) * t for p, t in zip(prev, target))

def target_crop(detection, w, h, zoom=1.4):
    box = detection.bounding_box
    cx = box.origin_x + box.width / 2
    cy = box.origin_y + box.height / 2
    return (cx, cy, w / zoom, h / zoom)  # centro no rosto, area reduzida pelo zoom

A cada frame eu calculo o corte alvo a partir do rosto, misturo com o corte anterior pela média exponencial e fixo o resultado dentro dos limites da imagem. Quando nenhum rosto é detectado, o enquadramento relaxa devagar de volta para o frame cheio, em vez de saltar.

05

A câmera virtual: a peça central

De nada adianta processar o vídeo se ele não aparece como webcam nos outros programas. Esse é o papel do pyvirtualcam, que no Windows usa o driver da câmera virtual do OBS Studio. Por isso o OBS precisa estar instalado uma vez (nem precisa abrir): ele registra o driver que o app reutiliza. O loop é simples: capturo da câmera física, aplico os efeitos e envio o frame para a câmera virtual.

import pyvirtualcam
from pyvirtualcam import PixelFormat

cap = cv2.VideoCapture(camera_index, cv2.CAP_DSHOW)
with pyvirtualcam.Camera(width=1280, height=720, fps=30, fmt=PixelFormat.BGR) as cam:
    while running:
        ok, frame = cap.read()
        if not ok:
            break
        frame = auto_framing(frame, ts)     # 1) enquadra no rosto
        frame = blur_background(frame, ts)  # 2) desfoca o fundo
        cam.send(frame)
        cam.sleep_until_next_frame()
câmera física
   -> OpenCV (captura)
      -> auto-framing  (FaceDetector + corte suavizado)
         -> blur de fundo (ImageSegmenter + composição alpha)
            -> pyvirtualcam (OBS Virtual Camera)
               -> Zoom / Meet / Discord / OBS

06

Uma armadilha real: capturar a própria câmera virtual

No teste de ponta a ponta apareceu um problema que vale registrar. Ao listar as câmeras pelo OpenCV, a OBS Virtual Camera também aparece como um dispositivo de entrada. Se o app captura ela por engano, você acaba enquadrando a tela de standby do OBS em vez do seu rosto, ou cria um loop de vídeo. A correção foi enumerar as câmeras pelo nome (via DirectShow) e filtrar qualquer dispositivo cujo nome contenha "OBS Virtual", deixando só as câmeras físicas na lista de entrada.

from pygrabber.dshow_graph import FilterGraph

VIRTUAL_HINTS = ("obs virtual", "obs-camera", "obs cam")

def list_cameras():
    devices = FilterGraph().get_input_devices()  # nomes na ordem de indice do OpenCV
    return [
        (i, name)
        for i, name in enumerate(devices)
        if not any(h in name.lower() for h in VIRTUAL_HINTS)
    ]

Para confirmar que o efeito realmente acontece, além da inspeção visual eu medi a variância do Laplaciano (um indicador de nitidez) no centro do frame, onde fica a pessoa, e num canto, onde fica o fundo. O centro deu cerca de 680 e o canto perto de zero, ou seja, fundo desfocado e pessoa preservada nítida, exatamente o esperado.

07

Resolvendo as queixas: bandeja e início minimizado

Os dois incômodos que me motivaram viraram requisitos explícitos. Primeiro, escopo: o app mexe só na câmera, ponto. Não toca em microfone nem em alto-falantes, porque simplesmente não lida com áudio. Segundo, comportamento de inicialização: um ícone na bandeja do sistema (com pystray) permite rodar minimizado, pausar e retomar; e o início com o Windows registra o app no boot já com a flag de iniciar minimizado, o oposto do Broadcast abrindo maximizado.

import winreg, sys

RUN_KEY = r"Software\Microsoft\Windows\CurrentVersion\Run"

def set_autostart(enabled):
    with winreg.OpenKey(winreg.HKEY_CURRENT_USER, RUN_KEY, 0, winreg.KEY_SET_VALUE) as key:
        if enabled:
            cmd = f'"{sys.executable}" --minimized'  # abre direto na bandeja
            winreg.SetValueEx(key, "CamFX", 0, winreg.REG_SZ, cmd)
        else:
            winreg.DeleteValue(key, "CamFX")

08

Empacotando como executável

Para virar algo instalável, gerei um executável único com o PyInstaller. O detalhe importante é empacotar junto os modelos .tflite do MediaPipe e os assets internos da biblioteca, para o app rodar sem depender de download na primeira vez. Os modelos são baixados automaticamente na primeira execução a partir do fonte e ficam em cache na pasta do usuário; no executável, vão embutidos.

pyinstaller --onefile --windowed --name CamFX \
  --collect-all mediapipe \
  --add-data "selfie_segmenter.tflite;models" \
  --add-data "blaze_face_short_range.tflite;models" \
  main.py

Resultado: um CamFX.exe que abre, deixa escolher a câmera, ligar blur e auto-framing com controles de intensidade, e manda o vídeo para a câmera virtual. No Zoom ou no Meet basta selecionar a OBS Virtual Camera como webcam. Fazendo só o que eu precisava, do jeito que eu queria.

FAQ

Perguntas frequentes

Por que precisa do OBS instalado?

O pyvirtualcam, no Windows, usa o driver da câmera virtual que o OBS Studio registra ao ser instalado. Você não precisa abrir o OBS, apenas instalá-lo uma vez para que o driver exista. A partir daí o CamFX cria a câmera virtual sozinho e o vídeo processado aparece como a webcam OBS Virtual Camera nos outros aplicativos.

Dá para rodar sem placa NVIDIA?

Sim. Diferente do NVIDIA Broadcast, que exige GPU NVIDIA RTX, o CamFX roda o MediaPipe na CPU por padrão. Nos meus testes, blur e auto-framing juntos em 720p rodaram em torno de 13 FPS na CPU, suficiente para chamadas; em máquinas mais fortes ou reduzindo a resolução, o número sobe.

Por que o efeito só na câmera e não no áudio?

Por escolha de escopo. O incômodo que me levou a construir a ferramenta era justamente o efeito vazar para microfone e alto-falantes quando eu só queria mexer na câmera. O CamFX não lida com áudio de propósito: ele captura vídeo, processa e expõe uma câmera virtual, e nada mais.

Uma ferramenta que faz só o que você precisa

Construir o próprio substituto do NVIDIA Broadcast me deu exatamente o que eu queria: blur e auto-framing só na câmera, início minimizado na bandeja e nenhum efeito indesejado no áudio. Se você tem um incômodo parecido com uma ferramenta grande demais para a sua necessidade, posso ajudar a desenhar e construir a solução enxuta certa.