Dann müssen wir da eben selbst was bauen …
Wer schon mal einen Streamer mit sauberem Webcam-Overlay gesehen hat — kein Greenscreen, einfach die Person freischwebend über dem Gameplay — der hat mit hoher Wahrscheinlichkeit NVIDIA Broadcast bei der Arbeit beobachtet. Eine App, die per GPU den Hintergrund in Echtzeit entfernt — und sie ist hervorragend. Saubere Kanten um die Haare, weiche Übergänge bei schnellen Bewegungen, null Konfiguration. Ich hab sie jahrelang unter Windows benutzt. Funktioniert einfach.
Dann habe ich mir vorgenommen auf Linux umzusteigen.
NVIDIA Broadcast gibt es nicht für Linux. Nicht „ist in der Beta“, nicht „gibt’s ’nen Workaround“ — es existiert schlicht und einfach nicht. Die zugrundeliegenden SDKs (Maxine, VFX SDK) sind zwar verfügbar, aber kostenpflichtig. NVIDIA möchte, dass ich für KI-Features auf der GPU, die ich bereits gekauft habe, nochmal extra zahle. Für ein Feature, das unter Windows kostenlos ist.
Ihr werdet euch jetzt fragen was das soll, warum macht der das? Ganz einfach:
1. habe ich keine AMD oder Intel GPU zur Hand, kann also nicht testen was Claude fabriziert.
2. Je weniger Code der verstehen muss desto besser funktioniert er (bilde ich mir zumindest ein).
Vor ein paar Monaten wäre das ein Dealbreaker gewesen. Ich bin kein Softwareentwickler. C++ und CUDA sind nicht meine Welt. Aber wir leben in interessanten Zeiten: Ich hab Claude Code aufgemacht und angefangen auszuprobieren was damit inzwischen so geht.
Die Diagnose
obs-backgroundremoval ist ein beeindruckendes Stück Open-Source-Software. Vier Jahre Entwicklung, acht KI-Modelle, Support für jede Plattform und jeden GPU-Hersteller — Windows, macOS, Linux, NVIDIA, AMD, Intel, CPU-Fallback. Das ist ein ausgereiftes Projekt, und was jetzt kommt, ist ausdrücklich kein Bashing des Projekts oder seines Autors. Die haben ein Cross-Platform-Plugin gebaut, das auf beinahe allem läuft. Was ich gemacht habe, ist das genaue Gegenteil: alles außer einem einzigen Ziel rauswerfen. Das ist kein besserer Ansatz — das ist ein anderer. Und genau darum geht’s in diesem Post: zeigen, was möglich ist, wenn man sich KI-Tools zunutze macht, um ein sehr spezifisches Problem zu lösen.
Für meinen Use Case — Linux, NVIDIA-GPU, Echtzeit-Streaming — war das Plugin nahezu unbenutzbar.
Das Plugin erledigt die gesamte KI-Schwerarbeit auf der CPU, während die GPU Däumchen dreht. Schlimmer noch: Es verarbeitet jedes Bild synchron — die gesamte OBS-Video-Pipeline steht still und wartet, während das KI-Modell grübelt, ob dieser Pixelhaufen jetzt eine Schulter ist oder ein Bücherregal. Das Resultat: Frame Drops, sichtbares Ruckeln, und Maskenkanten, die aussehen, als hätte jemand mit der Bastelschere um mich herumgeschnitten. Beim Streamen sieht das dann so aus wie im Video oben: Du bewegst den Kopf, und die Maske zieht eine halbe Sekunde später hinterher, außerdem ist sie nicht exakt genug.
Was nun?
Das Ziel: Echtzeit-Hintergrundentfernung in 1080p bei 30fps mit weichen Kanten in Broadcast-Qualität. Mit einer RTX 2060 Super.
Tabula Rasa
Die erste „Änderung“ war die gröbste. Ich hab Claude Code losgeschickt alles zu löschen was nicht „NVIDIA auf Linux“ war. Windows-Support — weg. macOS — weg. AMD, Intel, CPU-Fallback — alles weg. Vier Jahre sorgfältiger Cross-Platform-Arbeit mal eben entfernt.
Der reinste Vandalismus, sorry dafür. Aber sobald das alles raus war, wurde der kritische Pfad von einem Labyrinth aus #ifdefs zu Code, über den man tatsächlich nachdenken kann.
Manchmal ist das Produktivste, was man tun kann: Löschen.
Die Optimierung
Claude Code hat hier den gesamten Code geschrieben. Mein Job war: Ideen liefern, Richtungsentscheidungen treffen und jeden Zwischenstand testen. Ein Kreislauf aus „Versuch mal X“ → Code → Testen → „Nee, probier’s anders.“
Asynchrone Verarbeitung. Das Original-Plugin blockierte die gesamte OBS-Video-Pipeline während der Inferenz — jedes Bild wartete, bis die KI fertig war. Lösung: eine threadsichere Warteschlange. OBS schiebt Bilder rein, ein separater Thread verarbeitet sie im Hintergrund, und das nächste Bild greift sich das jeweils letzte Ergebnis. Frame Drops: sofort weg. Der Haken: Die Maske lief jetzt 2-3 Bilder hinter dem Video her. Würde ich später fixen. (Spoiler: Es wurde kompliziert.)
Speicher-Diät. Das Plugin kopierte komplette 1080p-Bilder — 8 Megabyte pro Stück — mehrfach pro Frame. Einmal fürs Preprocessing, einmal für den Worker-Thread, einmal für die Ausgabe. Bei 30fps sind das rund 750 MB/s reiner Abfall. Die Lösung: Statt Daten zu kopieren, einfach die Zeiger auf die Daten tauschen — der Computer merkt sich nur, wo das Bild liegt, statt es jedes Mal komplett umzuschaufeln.
GPU-Preprocessing. Fünf separate CPU-Operationen — Farbkonvertierung, Skalierung, Normalisierung, Formatkonvertierung, Layout-Transposition — ersetzt durch einen einzigen GPU-Kernel, der alles in einem Durchgang erledigt. Preprocessing sank von 3ms auf 1,8ms. Kein Gamechanger allein, aber bei einem Gesamtbudget von 33ms pro Frame zählt jede Millisekunde.
Wissen, wann man aufhört. Das Postprocessing (Glättung, Kantenbereinigung) operierte auf 61 Kilobyte Daten. Das passt komplett in den CPU-Cache. Das auf die GPU zu verschieben wäre tatsächlich langsamer gewesen, weil allein der Datentransfer länger dauern würde als die Berechnung selbst. Nicht alles muss auf der GPU laufen. Manchmal ist die langweilige Antwort die richtige.
TensorRT. Automatische Erkennung und Engine-Caching für einen Extra-Geschwindigkeitsboost auf Systemen, die es unterstützen (also RTX 2000 und neuer) — mit sauberem Fallback auf CUDA, wenn nicht.
Die Zahlen sahen auf dem Papier besser aus.
Aber die Maske hing immer noch sichtbar hinter meinen Kopfbewegungen her:
Wo es schmerzhaft wurde
Verdächtiger Nr. 1: Frame-Skipping. Das Plugin hatte einen „Ähnlichkeitscheck“, der aufeinanderfolgende Bilder in voller Auflösung verglich, um zu entscheiden, ob die Inferenz übersprungen werden kann. Klingt clever — warum identische Frames verarbeiten? Nur dass es 8-Megabyte-Bilder Pixel für Pixel verglich und 57% aller Frames übersprang. Abgeschaltet. Lagged immer noch.
Verdächtiger Nr. 2: Masken-Qualität. Wechsel von harten Binärmasken (jeder Pixel ist entweder „Du“ oder „nicht Du“) auf weiche Alpha-Matten — die Rohwahrscheinlichkeit des KI-Modells, bei der Kanten sanfte Übergänge bekommen statt harter Stufen. Echte Qualitätsverbesserung. Lagged immer noch.
Sackgasse: FP16. Halbpräzisions-Version des Modells geladen. OBS crashte beim Start — das Modell erwartete 16-Bit-Eingaben, der Code lieferte 32-Bit. Versuch, das Modell manuell zu konvertieren. Ergebnis: ein kaputter Berechnungsgraph, in dem manche Operationen 16-Bit und andere 32-Bit bekamen. Aufgegeben. Manchmal gewinnt man, manchmal lernt man, und manchmal löscht man den Branch und tut so, als wäre nichts gewesen.
Der Profiler rettet den Tag. NVIDIAs Nsight Systems — ein Profiling-Tool, das zeigt, wo genau die Zeit verbrannt wird. Die Ergebnisse waren ernüchternd:
| Stufe | Zeit |
|---|---|
| Nachbearbeitung (Kantenverfeinerung) | 13,9ms |
| KI-Modell-Inferenz | 7,7ms |
| GPU-Preprocessing | 1,8ms |
Der Kantenverfeinerungs-Filter, den wir zur Verbesserung der Qualität hinzugefügt hatten, brauchte fast doppelt so lang wie die KI-Inferenz selbst. Die „Verbesserung“ war der Flaschenhals. Rausgerissen. Lektion gelernt: Vorher und nachher profilen, nicht nur vorher.
Lagged immer noch. Ohne den Flaschenhals lag die Inferenz bei etwa 10ms. Mehr als schnell genug. Also warum hing die Maske immer noch hinterher? Ich hab Claude analysieren lassen bis der Groschen fiel.
Die asynchrone Pipeline selbst war das Problem. Sie fügt bauartbedingt 2-3 Frames Latenz hinzu — der Worker-Thread verarbeitet immer ein Bild aus der Vergangenheit. Bei 30fps sind das 66-100ms Verzögerung. Sichtbar. Störend. Innerhalb einer asynchronen Architektur nicht behebbar.
Aber: 10ms Inferenz passen locker in ein 33ms-Frame-Budget. Das Modell war schnell genug, um synchron zu laufen — Bild verarbeiten und Ergebnis im selben Tick liefern, null Latenz. Die asynchrone Queue, die uns am Anfang vor Frame Drops gerettet hatte, hielt uns jetzt zurück.
Ein simpler Check: Wenn das Modell schnell genug ist, Queue überspringen und die Inferenz direkt inline ausführen. Die Maske ging von „hinkt hinterher“ zu „trackt viel besser.“ Null Frames Verzögerung (nicht die Maske sondern die Pipeline, das merkt man nur wenn man davor sitzt und das Video hinter den eigenen Bewegungen ein bisschen her hinkt).
Lies das Paper, nicht nur die API
Nach dem gelöschten Code, den GPU-Kernels, der Async-dann-doch-Sync-Pipeline, dem Profiling und den Sackgassen — kam die mit Abstand größte Verbesserung des ganzen Projekts. Und sie kam weder aus Code-Optimierung noch aus meiner Richtung. Claude hat sich das Research Paper zum KI-Modell durchgelesen.
Das Plugin fütterte das KI-Modell (RVM — Robust Video Matting) mit vorgeschrumpften 320×192-Bildern und streckte die resultierende winzige Maske dann wieder auf 1080p. Natürlich waren die Kanten matschig. Man kann eine 320 Pixel breite Maske nicht einfach hochskalieren und knackige Haarsträhnen erwarten.
Was Claude im Paper fand: RVM hat einen eingebauten Deep Guided Filter. Das Modell ist dafür konzipiert, das volle 1080p-Bild zu empfangen, es intern für die schnelle Inferenz herunterzurechnen, und dann das originale hochauflösende Bild als Orientierung zu nutzen, um die Maske auf volle Auflösung hochzuschärfen. Es benutzt die tatsächlichen Kanten im Webcam-Bild — Haare, Finger, die Konturen des Hoodies — um pixelgenaue Grenzen zu erzeugen. Schon besser.
Das Plugin umging das komplett. Durch das Vorverkleinern des Bildes und downsample_ratio=1.0 bekam der Guided Filter identische „niedrigauflösende“ und „hochauflösende“ Eingaben. Er war effektiv ein No-Op — er lief zwar, hatte aber nichts, womit er arbeiten konnte. Das beste Feature des Modells wurde einfach übersprungen.
Der Fix:
static constexpr int INPUT_WIDTH = 1920;
static constexpr int INPUT_HEIGHT = 1080;
static constexpr float DOWNSAMPLE_RATIO = 0.25f;
Drei Zeilen. Gib ihm das echte Bild. Lass das Modell das tun, wofür es gebaut wurde.
Das Backbone läuft intern weiterhin auf niedriger Auflösung, aber der Deep Guided Filter muss jetzt bei voller 1080p arbeiten — die GPU hat mehr zu tun, passt aber immer noch bequem ins 33ms-Budget. Ich hab den Stream gestartet, die Kamera angemacht und es war tatsächlich besser. Von „ausgeschnitten mit der Bastelschere“ zu beinahe natürlichen Kanten.
Das Ergebnis
| Vorher | Nachher | |
|---|---|---|
| Frame Drops bei 1080p | Häufig | Keine |
| Masken-Latenz | 2-3 Frames (66-100ms) | 0 Frames |
| Kantenqualität | Hart, zackig | Weichere Übergänge |
| GPU-Auslastung | ~15% | ~70% |
| Gesamte Frame-Zeit | >33ms (zu langsam) | ~20ms (Luft nach oben) |
An dieser Stelle machen wir einen Break, die Lösung ist immer noch nicht wirklich brauchbar, aber schon eine ganze Ecke weiter. Vor allem 70% GPU Auslastung „nur dafür“, das muss noch besser gehen. Doch dazu mehr im nächsten Beitrag.
Zum Abschluss möchte ich noch einmal reflektieren.
Vor einem Jahr wäre dieses Problem für mich unlösbar gewesen. Nicht „schwierig“ — unlösbar. GPU-optimierte Echtzeit-Video-Pipelines in C++ sind keine Sache, die man sich an einem Nachmittag beibringt. Aber mit Tools wie Claude Code verschiebt sich, was „zu schwierig“ bedeutet. Nicht, weil die KI alles besser kann — sie braucht Richtung, Feedback, jemanden, der testet und entscheidet. Aber sie gibt einem die Möglichkeit, Probleme anzupacken, die vorher schlicht außer Reichweite waren. Es bleibt also spannend.
Das hier ist Teil 1 meiner Linux-Journey. Als Nächstes: Die Middleware komplett rauswerfen — eine eigenständige TensorRT-Pipeline, die direkt von der Kamera zur virtuellen Webcam geht, in unter 7ms. Die Reise geht weiter.
-GF