Pimp my webcam (stream)

Motivation

In unserer wöchentlichen Videokonferenz (‘Corona’) hat Kresse vor Kurzem (April 2020) etwas über ‘webcamoid’ erzählt. Das klang interessant! Gleich am nächsten Tag habe ich mir die Installation (auf Mint 19.3) vorgenommen, scheiterte aber (nach erfolgreichem make) zunächst am ‘make install’ für akvcam mit einer obskuren Fehlermeldung (file crypto irgendwas not found???). Also wieder deinstalliert mit ‘make uninstall’, das klappte einwandfrei. Trotzdem gab ich zunächst auf … Am übernächsten Tag hatte ich mir vorgenommen, die Sache genauer zu untersuchen. Wie sich herausstellte, war bis auf diese ‘überflüssige’ Fehlermeldung alles ok! (Hinweis: Das Kompilieren des Kernel-Moduls ist bei Kernel-Update/Änderung erneut erforderlich …)

Das Kernelmodul ‘akvcam’ installiert zwei zusätzliche - virtuelle - Video-Devices, eins zum Reinschreiben (‘output’), eins zum Auslesen (‘capture’). Wenn man jetzt den originalen Webcam-Stream mit einem Programm entgegen nimmt und ggf. verfremdet (‘pimped’) kann man diesen an den Videoport zum Reinschreiben übergeben und an den Videoport zum Auslesen (‘virtual camera’) einen beliebigen Client für die Wiedergabe hängen!

Streamflow also wie folgt:

Webcam->/dev/video(x)->Bsp. ‘webcamoid’ u.ä.->/dev/video(n)->akvcam->/dev/video(n+1)->z.Bsp. Browser (Jitsi)

Zum Testen nehme ich ‘guvcview’, danach kann man mit dem Browser (WebRTC) dieses virtuelle Gerät ebenfalls nutzen (Nicht: Parallel!). Komplett störungsfrei funktioniert z.Bsp. Jitsi derzeit (5/2020) im Chromium-Browser, in Brave geht nur die Kachelanzeige, im Firefox klappt es mal/mal nicht …

In der Ausgangskonstellation habe ich ‘webcamoid’ getestet. Nett (viele parametrierbare Effekte), aber irgendwie sinnlos! Wie wäre es stattdessen mit eigenem ‘Aufbrezeln’ mittels OpenCV/Python? In der Nutzungsanleitung zu ‘akvcam’ wird gezeigt, wie man (testweise) Streams an das Videodevice schicken kann, d.h. so einen Stream müsste man nur selbst produzieren, ins Device schreiben - et voilà, ein selbstgebauter ‘custom’ Stream! (Vespasian wendet ein: Nimm doch OBS - aber das ist nicht DIY … langweilig!)

Ein wenig Internet-Recherche ergab: Video Frames können aus OpenCV/Python über stdout als Bytes direkt ausgegeben werden (ja, auch auf die Konsole … ;). Damit stehen einfachen Manipulationen Tür und Tor offen!

Hier mal die Resultate, zwecks Vergleich

vorher:

ohne

und nachher:

ohne

Hint: Die Kamera war wohl abgeklebt …

Installationshinweise

  1. Zunächst muß akvcam installiert werden (https://github.com/webcamoid/akvcam). Einfach mit git clonen oder als ZIP runterladen (dann entpacken im eigenen Benutzerverzeichnis).
  2. Verzeichnis umbenennen auf akvcam
  3. cd ~/akvcam/src
  4. make (Übersetzen sollte einwandfrei durchlaufen …)
  5. sudo make install (hier kommt die überflüssige Fehlermeldung)
  6. sudo depmod -a
  7. ls /lib/modules/$(uname -r)/extra/akvcam.ko* (Prüfen, ob im Zielverzeichnis angekommen …)
  8. sudo mkdir -p /etc/akvcam (Konfigurationsdatei erstellen)
  9. sudo touch /etc/akvcam/config.ini
  10. config.ini editieren (Daten siehe https://github.com/webcamoid/akvcam/blob/master/ports/ci/travis/config.ini)
  11. sudo chmod -vf 600 /etc/akvcam/config.ini
  12. ls /dev/video* (Bekannte Videoports des eigenen Rechners merken)
  13. sudo modprobe videodev
  14. sudo insmod akvcam.ko (Jetzt Kernelobject aktivieren)
  15. ls /dev/video* (Neu hinzu gekommene - virtuelle - Videoports gehören zur akvcam)
  16. cat /dev/urandom > /dev/video4 (White noise kann z.Bsp. mit ‘guvcview’ getestet werden)
  17. cat /dev/video5 (Test: Stream von virtuellem Gerät auf Konsole ausgeben …)
  18. sudo apt install virtualenv (Falls für Python keine virtuellen Umgebung zur Verfügung stehen …)
  19. virtualenv cv –python=“python3” (Ansonsten eins für OpenCV vorbereiten)
  20. source cv/bin/activate (Aktivieren nicht vergessen!)
  21. pip3 install opencv-python (Nur in die virtuelle Umgebung OpenCV installieren)
  22. python3 -c “import cv2” (Und testen …)

Programmaufteilung

Da ich die (virtuelle) Kamera nicht immer aktiv haben möchte (wer weiss, vielleicht war die ‘crypto’ Fehlermeldung das letzte Röcheln des integrierten Bitcoin-Schürf-Clients …), habe ich ein bash-Script vorgesehen mit folgendem Ablauf:

  1. akvcam Kernelmodul aktivieren
  2. ‘cv’ python environment aktivieren
  3. Python Script laufen lassen mit Ausgabeumleitung an ersten virtuellen Videoport
  4. Extern –> Webcam-Applikation (guvcview, browser etc.) an zweiten virtuellen Videoport
  5. Extern –> Webcam-Applikation besser als erstes wieder stoppen …
  6. Benutzerabbruch im Script erfolgt mit Strg-C
  7. akvcam (die virtuellen Kameras) werden wieder entladen (System wieder sauber!)

virtcam.sh:

echo "Installing video4 (output) / video5 (capture) virtual devices ..."
cd ~/akvcam/src
sudo modprobe videodev
sudo insmod akvcam.ko
echo "Starting 'stream modifier' application ..."
echo "Activating (cv) environment ..."
source ~/cv/bin/activate
cd ~/cv
ls /dev/video?
python3 funcam.py > /dev/video4 # video4 is output device -> video4 is capture
echo "Removing virtual (camera) devices ..."
sudo rmmod akvcam.ko
echo "Done."

Die eigentliche Action erfolgt im Python-Script (‘funcam.py’):

import numpy as np
import datetime, time
import sys
import cv2

# Hint: print() to stderr (fh#2) as the actual video stream goes to stdout (fh#1)!
print("funcam started ...",file=sys.stderr)

cap = cv2.VideoCapture(0) # TODO: You have to verify, if /dev/video0 is your actual webcam!
if not cap.isOpened(): # if not open already
    cap.open() #make sure, we will have data
# Adjust channel resolution & use actual
cap.set(3, 640)
cap.set(4, 480)

while(cap.isOpened()):
    try:
        ret, frame = cap.read()
        if ret==True:

            frame4 = cv2.cvtColor(frame.copy(),cv2.COLOR_BGRA2RGB) #   Correct color space

            # Now: The fun stuff! -------------------------------------------------------------------------------------------

            # ... <your job in here, use cv2.line/cv2.rectangle etc. freely but be careful w/ time consuming operations!> ...

            # As a sample, we display the current time
            t = time.strftime("%H:%M:%S", time.localtime())
            cv2.putText(frame4, t, (505,472), (cv2.FONT_HERSHEY_DUPLEX), 1.4, (0, 0, 0), 4, cv2.LINE_AA)

            # End of fun stuff! ---------------------------------------------------------------------------------------------

            # Write raw output (pipe/redirect to video device externally!)
            sys.stdout.buffer.write(frame4.tobytes())
        else:
            break
    except:
        break

cap.release()
print("\nfuncam terminated.",file=sys.stderr)

Arbeitsablauf

  1. Bash Script ‘funcam.sh’ laufen lassen (in Terminalfenster)
  2. Client starten, für Videokonferenzen (z.Bsp. mit Jitsi) Browser mit passendem Link starten
  3. … Blabla, Bullshit … Bingo! …
  4. Client beenden
  5. Im Terminalfenster Script ordentlich beenden (mit Strg-C!)

Und hier ein klein wenig ‘Live’ zum Anfixen mit einfachen Effekten:

Nachtrag/Update

  • Die aktuelle Version liegt hier: https://git.hacknology.de/projekte/PimpedWebcam
  • Die Version kann mittlerweile erheblich mehr (Aufrufbeispiele im bash-Script), z.Bsp.
    • Verbesserte Argument-Übergabe (sehr viel parametrierbar)
    • Hintergrundsubstitution (noch nicht wirklich perfekt - aber ohne Green/Bluebox …)
    • Variable RSS-Feeds
    • Alarm einstellbar (Uhrzeit blinkt rot)
    • 3D Schrifthintergründe, TrueType-Fonts möglich
    • Video als Teil des Hintergrunds abspielbar (x,y,w,h)

Was bleibt zu tun?

  • Greenscreen (beta) verbessern
  • Andere Auflösungen - insbesondere 1280x720 - möglich?
  • … whatever …