Webcam benutzen in C#

Wie steuert man am besten und einfachsten eine Webcam unter Benutzung von C#.Net an? Recherchiert man im Internet, wird man, sofern man überhaupt fündig wird, leider oftmals mit sehr langen Artikeln und unübersichtlichen Snippets konfrontiert. Doch das muss gar nicht sein, denn mit Hilfe der AForge.NET Library geht das ganz einfach. Und wie das genau geht, möchte ich euch im Folgenden Artikel zeigen.

Was wird für dieses Tutorial benötigt?

  • Die AForge.Video.dll, sowie die AForge.Video.DirectShow.dll
    Beide Dlls findet ihr auf der AForge Downloadseite im “(libs only)” zip-Archiv.
  • Eine schlichte WinForms-Anwendung, auf der ihr schon mal eine Picturebox anlegt.
  • Eine Webcam. Z.B. diese hier:

    Keine Produkte gefunden.

  • C#-Grunlagenwissen. Wenn nicht vorhanden, lies das hier:

    Keine Produkte gefunden.

Zuerst bindet ihr die beiden oben genannten Dlls als Referenzen ein. (Ich gehe an dieser Stelle einfach mal davon aus, dass ihr irgend eine Version des Microsoft Visual Studios für die Entwicklung nutzt. Bei anderen IDEs dürfte dieser Schritt jedoch ähnlich sein.)
Bevor wir zum Quellcode kommen, müsst ihr nur noch zwei Eventhandler anlegen. Einen für das FormLoad-Event und einen für das FormClosed-Event. (Am einfachsten geht dies direkt über den Designer des Visual Studios.)
Der Rest erklärt sich über den nachfolgenden Quelltext und dessen Kommentare.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
//Anlegen von Using-Direktiven für einfacheren Zugriff auf die
//benötigten Methoden der AForge Library
using AForge.Video;
using AForge.Video.DirectShow;

namespace aforgeWebcamTutorial
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        //Anlegen eines Webcam-Objektes
        VideoCaptureDevice videoSource;

        private void Form1_Load(object sender, EventArgs e)
        {
            //Anlegen einer Liste mit allen Videoquellen. (Hier können neben der gewünschten Webcam
            //auch TV-Karten, etc. auftauchen)
            FilterInfoCollection videosources = new FilterInfoCollection(FilterCategory.VideoInputDevice);

            //Überprüfen, ob mindestens eine Aufnahmequelle vorhanden ist
            if (videosources != null)
            {
                        //Die erste Aufnahmequelle an unser Webcam Objekt binden
                        //(habt ihr mehrere Quellen, muss nicht immer die erste Quelle die
                        //gewünschte Webcam sein!)
                        videoSource = new VideoCaptureDevice(videosources[0].MonikerString);

                        try
                        {
                            //Überprüfen ob die Aufnahmequelle eine Liste mit möglichen Aufnahme-
                            //Auflösungen mitliefert.
                            if (videoSource.VideoCapabilities.Length > 0)
                            {
                                string highestSolution = "0;0";
                                //Das Profil mit der höchsten Auflösung suchen
                                for (int i = 0; i < videoSource.VideoCapabilities.Length; i++)
                                {
                                    if (videoSource.VideoCapabilities[i].FrameSize.Width > Convert.ToInt32(highestSolution.Split(';')[0]))
                                        highestSolution = videoSource.VideoCapabilities[i].FrameSize.Width.ToString() + ";" + i.ToString();
                                }
                                //Dem Webcam Objekt ermittelte Auflösung übergeben
                                videoSource.VideoResolution = videoSource.VideoCapabilities[Convert.ToInt32(highestSolution.Split(';')[1])];
                            }
                        }
                        catch { }

                        //NewFrame Eventhandler zuweisen anlegen.
                        //(Dieser registriert jeden neuen Frame der Webcam)
                        videoSource.NewFrame += new AForge.Video.NewFrameEventHandler(videoSource_NewFrame);

                        //Das Aufnahmegerät aktivieren
                        videoSource.Start();
            }

        }

        void videoSource_NewFrame(object sender, AForge.Video.NewFrameEventArgs eventArgs)
        {
            //Jedes ankommende Objekt als Bitmap casten und der Picturebox zuweisen
            //(Denkt an das ".Clone()", um Zugriffsverletzungen aus dem Weg zu gehen.)
            pictureBoxVideo.BackgroundImage = (Bitmap)eventArgs.Frame.Clone();
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            //Beim Beenden des Programmes die Webcam wieder "freigeben",
            //damit sie von anderen Anwendungen benutzt werden kann.
            if (videoSource != null && videoSource.IsRunning)
            {
                videoSource.SignalToStop();
                videoSource = null;
            }
        }

    }
}

Ich hoffe, dass ihr nun auch im Stande seit, erfolgreich Webcams und andere Videoaufnahmequellen unter C# zu verwenden.
Solltet  ihr noch Fragen, Anregungen oder Kritik haben, so schreibt mir doch einfach einen Kommentar.

Viele Grüße,
Raffi

31 Kommentare

  1. Henningsays:

    Hallo Raffi,

    danke für die schöne Anleitung.
    Es gibt ein kleines nerviges Problem:
    (Visual Studio 2012 auf Windows 7 32-Bit.)

    Wenn ich Dein Programm als .Net 3.5 – Projekt compiliere läuft alles gut.
    Dagegen tritt beim Compilieren als .Net 4.5 – Projekt folgender Effekt auf:
    Das Programm lässt sich nicht mehr in VS2012 debuggen.
    Beim Starten mit F5 erscheint kurz das Fenster und das Programm wird sofort ohne Fehlermeldung beendet. Ohne Debuffen (Strg-F5) kann das Programm weiterhin normal gestartet werden.

    Das gleiche Problem tritt bei der Camera_Net Library auf, die Du vielleicht kennst:
    http://www.codeproject.com/Articles/671407/Camera-Net-Library
    Da dort auch directshow verwendt wird, wird die ursache wahrscheinlich dort liegen.

    viele Grüße,
    Henning

  2. mennowarsays:

    Mahlzeit.
    Ich bekam beim wechseln der Auflösung und neu starten der videoSource öfters mal den Fehler das das Objekt bereits verwendet wird.
    Eine Lösung war der picturebox nicht das Bild selber aus dem Event zuzuweisen (auch nicht per clone :) ), sondern das ganze über die Graphics zu lösen.

    Also an der Stelle an der die videoSource gestartet wird setzte ich ein Bild mit den entsprechenden Maßen in die pictureBox per:

    pictureBoxVideo.Image = new Bitmap(videoSource.VideoResolution.FrameSize.Width, videoSource.VideoResolution.FrameSize.Height, PixelFormat.Format16bppRgb555);

    Damit ist sichergestellt das ein ordentliches Bild der PB zugrunde liegt mit genau den Abmessungen die hinterher zu erwarten sind (was für mich wichtig ist, da ich die SizeMode der PB dynamisch beim resize der Form anpasse).

    Im videoSource_NewFrame läuft nun klaglos das folgende Konstrukt:

    var b2 = (Bitmap)eventArgs.Frame.Clone();
    using (var gr = Graphics.FromImage(pictureBoxVideo.Image))
    {
    gr.DrawImage(b2, 0, 0);
    gr.Flush();
    pictureBoxVideo.Refresh();
    }

    b2.Dispose();

    Läßt sich warscheinlich noch mit DoubleBuffered etc. optimieren, aber zumindest tut es so.
    Ich dachte das würde evtl. jemanden interessieren.

  3. Harrysays:

    Hallo,
    Ich weiß dein Artikel ist schon ne weile her, aber grade erst Gefunden.
    Ich finde den echt Toll :-)
    Aber hab ne frage:
    Weiß du ob man den Namen der Kamera auslesen kann oder Prüfen ob es eine echte oder eine Virtuelle ist?
    Ich habe aktuell das Problem das die 1 gefundene ManyCam ist was jedoch nicht immer läuft.

    • Hallo Harry,

      du könntest in Zeile 30 des obigen Beispiels folgende Erweiterung vornehmen:

      FilterInfoCollection videosources = new FilterInfoCollection(FilterCategory.VideoInputDevice);

      for (int i = 0; i < videosources.Count; i++) { string camName = videosources[i].Name; string camHardwareId = videosources[i].MonikerString; } Für deinen Fall könntest du a) schauen, ob in camName "ManyCam" steht oder b) in der HardwareID rausparsen, ob es sich um ein USB-Gerät handelt. Viele Grüße

  4. NoBosays:

    Hallo zusammen.
    Der Quellcode funktioniert bei mir einwandfrei. Ich habe das Programm mit C# 2010 auf einem Win8 Rechner erstellt und getestet. Es läuft auch auf einem Rechner mit XP. Aber nicht auf einem mit Win7. Die Kamera wird erkannt, aber es wird kein Bild angezeigt. Beim debuggen, habe ich festgestellt, das die Auflösungen nicht erkannt werden (640×480). Habe zurzeit keine Idee wo ich ansetzen soll.

  5. Ich habe festgestellt dass das laden des Bildes in die Picturebox RAM frisst weil das vorherige Bild nicht wieder freigegeben wird – außerdem ist die CPU beanspruchung recht hoch, darum habe ich das laden des BIldes wie folgt abgeändert.

    void videoSource_NewFrame(object sender, AForge.Video.NewFrameEventArgs eventArgs)
    {
    // eine halbe Sekunde warten bevor das nächste Bild geladen wird um die CPU zu schonen
    System.Threading.Thread.Sleep(500);
    // Ich benutze hier die Image-Eigenschaft der PictureBox, weil ich das Bild nur einmal brauche. Falls dieses schon einmal geladen wurde, wird es hier geleert um den RAM zu schonen
    if (pictureBox1.Image != null) pictureBox1.Image.Dispose();
    //Jedes ankommende Objekt als Bitmap casten und der Picturebox zuweisen
    //(Denkt an das “.Clone()”, um Zugriffsverletzungen aus dem Weg zu gehen.)

    pictureBox1.Image = (Bitmap)eventArgs.Frame.Clone();
    }

    Vielleicht braucht das ja der ein oder andere von euch :-)

    • Hallo Sven,

      danke für den Hinweis. Jedoch kann ich dein Problem nicht ganz nachvollziehen. Ob ich die Image oder die BackgroundImage-Eigenschaft nehme, macht meines Wissens keinen Unterschied. Ich habe sie nur gewählt, da man bei der BackgroundImage-Eigenschaft das ImageLayout (Zoom, Stretch, etc.) festlegen kann.

      Da ich da Objekt, was der BackgroundImage-Eigenschaft zugewiesen wurde, überschreibe, dürfte es zu keinem größeren RAM Verbrauch kommen als bei deiner Lösung. Das Dispose() ist in meinen Augen somit unnötig. Sollte der Speicher eng werden, müsste der GC das von alleine aufräumen.

      Gefährlicher hingegen finde ich dein Thread.Sleep() im UI-Thread. Das blockt, wenn auch nur minimal, das UI, was meiner Meinung nach wesentlich unschöner ist.

      Ein Grund warum es zu lags kommen kann, könnte jedoch tatsächlich an der Sache liegen, die du mit deinem Thread.Sleep() zu umgehen versuchst. Was ist, wenn die Webcam schneller Frame liefert, sprich das NewFrameEvent feuert, als die Picturebox das Bild zeichnen kann? Dann müssten sich Image-Objekte aufhäufen/anlagern.
      Hier wäre es statt Thread.Sleep aber angebrachter entweder die Framerate der Webcam-Aufzeichnung zu minimieren (müsste seitens AForge gehen) oder im NewFrame-Event nur die Zuweisung zur Picturebox zu machen, wenn diese gerade nicht am Zeichnen ist. Hier für müsste man das PaintEvent der PictureBox überschreiben, sodass man einen Status von der Picturebox auslesen kann, ob diese gerade zeichnet.

      Viele Grüße,
      Raffi

      • Entschuldige bitte – das mit dem wechseln vom BackgroundImage zu einem Image war kein Problem, sondern eine Design-Entscheidung von mir, da das Bild im image nur ein mal dargestellt wird und das Bild im BackgroundImage so oft dupliziert und hintereinander angeordnet wird wie die PictureBox groß ist.

        Natürlich gebe ich dir recht das das Thread.Sleep() nur ein frickeliger ansatz ist die Frames der Webcam langsamer in die PictureBox zu laden um die CPU zu entlasten – ich hatte nicht genauer drüber nachgedacht und spätestens bei den nächsten Tetsts wäre mir dann aufgefallen das die GUI blockiert :-)

        Das Dispose() hingegen bringt zumidnest bei mir das nicht 300MB RAM verbraucht werden müssen um danach wieder freigegeben werden zu können – sieht im RAM Verlauf des Taskmanagers vom Windows sehr unschön aus :-/

        Vielen Dank auf jeden Fall für die Infos! Ich werde mal bei AForge schauen ob ich was zum Einstellen der Framerate finde :-)

      • Thomassays:

        Wenn eine Klasse IDisposable implementiert, ist man als Verwender der Klasse dazu verpflichtet Dispose aufzurufen, wenn man die Instanz nicht mehr braucht. Alles andere führt ins Verderben.

        Es geht hier nicht um die Freigabe von Speicher, sondern um die Freigabe der unmanaged Ressourcen wie z.B. OS-Handles. Habe schon selbst erlebt, wie eine Produktion lahmgelegt wurde, weil jemand vergessen hat Dispose für dynamisch erstellte Controls aufzurufen. Wenn die Handles ausgehen bevor aufgrund von Speichermangel der GC losrennt und die Finalizer auslöst, hat man ein riesiges Problem.

  6. Chrissays:

    MetriCam http://www.metricam.net wäre auch noch eine Alternative, damit lassen sich neben Webcams auch noch einfach 3-D Kameras, wie Kinect oder Time-of-Flight Kameras unter .NET ansteuern. Ist auch umsonst.

    • Danke für den Hinweis. Ich werde mir das mal anschauen und gegebenenfalls noch einen Artikel darüber veröffentlichen.

  7. Geert Booonstrasays:

    Hallo, Ich habe Mühe wie FormClosed und Form Open Event.
    Können Sie vmir das noch näher erklären? Vielen Dank.
    Geert

  8. Nataschasays:

    Hey :)
    Eine Frage hätte ich noch zu deinem Programm – kann ich die Auflösung selber ändern oder ist das schon in den eingebundenen Dateien festgelegt?
    LG Natascha

    • Raffisays:

      Hallo Natascha,

      in den Zeilen 40-57 im obigen Code wird versucht die höchste Auflösung der Kamera zu wählen. Wenn du selbst die Auflösung festlegen möchtest kannst du alle verfügbaren Auflösungen aus dem Array “videoSource.VideoCapabilities” auslesen. (Siehe zum Beispiel Zeile 50).
      Wenn du dich für eine Auflösung entschieden hast, musst du sie dem Attribut “videoSource.DesiredFrameSize” zuweisen. (Siehe Zeile 54)

      In solchen Fällen hilft es, wenn du mal einen Breakpoint setzt und mit dem Debugger nachschaust, in welchem Format die Werte, in diesem Fall für die Auflösung, vorliegen. Das hilft mir immer, solche Dinge besser anzuprogrammieren.
      Wenn du nicht wissen solltest, wie man mit dem Debugger umgeht, dann kann ich gerne einen kleinen Artikel mit Basics zum Thema Debugging schreiben.

      Viele Grüße,
      Raffi

  9. TheAskersays:

    Hallo Raffi,

    1.Frage: was ist das?

    Die AForge.Video.dll, sowie die AForge.Video.DirectShow.dllBeide Dlls findet ihr auf der AForge Downloadseite im “(libs only)” zip-Archiv

    Kann ich das Programm dann nur auf meinem Rechner laufen lassen?

    2. Wie kann ih das komplett als videodatei speichern was ich aufgenommen habe?

    3.Wie kann ich nur ein Foto machen?

  10. Natschasays:

    Also ich fand deine Anleitung echt gut erklährt und sehr hilfreich. Nur stehe ich jetzt vor einem weiterem Problem. Ich will mit der Webcam ein Bild von einem einfachen Viereck(mit einem dicken schwarzen Stift auf einem weißem Blatt gezeichnet) aufnehmen und die Linien vektorisieren um sie nacher in einem anderen Programm wiedergeben zu können.. Weißt du ungefähr wie das gehen könnte oder wo ich Infos dazu finde?
    Das wäre echt toll :)

    • Raffisays:

      Was genau meinst du mit vektorisieren? Reichen die die Koordinaten oder muss das Bild direkt in einem gültigen Vektorgrafikformat ausgegeben werden?

      Du könntestest das Bild nehmen und dir ein Bitmapobject anlegen. Dann gehst du mittels for-Schleifen durch alle Pixel.
      for (int x = 0; x < bmp.width; x++) { for (int y = 0; y < bmp.height; y++) { bmp.GetPixel(x,y); } }

      In //Do work könntest du die einzelnen Pixel abrufen mit bmp.GetPixel() und dann den Farbwert auslesen. Wenn er im Schwarzbereich liegt, merkst du ihn dir. Somit suchst du die vier schwarzen Pixel die am weitesten außen liegen. Das sind dann die 4-Ecken deines Rechtecks für die Vektorgrafik.

      Solltest du das wirklich so machen, solltest du noch nach "Bitmap LockBits" suchen. Mein Vorschlag dürfte zwar auch funktionieren, aber ist leider sehr langsam. Schneller als die GetPixel Methode ist der Weg über Lockbits. Dort wird das Bitmap-Objekt als ByteArray betrachtet. Dies ist wesentlich performanter.

      Ich hoffe ich konnte dir helfen. Wenn du noch Fragen hast, schreib noch einen Kommentar. ;)

      Viele Grüße,
      Raffi

      • Nataschasays:

        Naja es geht darum, dass ein Legoroboter, der auch mit C# angesteuert wird, das Bild nachzeichnen soll. Da das nur schwarz-weiß ist, sollte das mit den Pixel eigentlich auch funktionieren..
        Und wie kann ich ein Bitmapobjekt anlegen – dazu brauche ich dieses Bitmap LockBits oder?
        Lg Natascha

        • Nataschasays:

          Und irgendwie bin ich gerade zu dumm mit der Webcam ein Bild aufzunehmen, das müsste aber schon über einenm RadioButton gehen oder?
          Lg Natascha

          • Raffisays:

            Einen RadioButton? Irgendwie kann ich mir gerade schwer vorstellen, was du machen willst. Ich kann die anbieten, dass du mir den Quelltext zuschickst und ich mal einen Blick darauf werfe. Ansonsten bräuchte ich wenigstens mal Auszüge, wo es hakt.

            Alternativ kann ich dir auch noch http://www.mycsharp.de empfehlen. In dem Forum wird einem eigentlich auch immer schnell und kompetent geholfen, solange man freundlich und lernbereit ist.

            Viele Grüße,
            Raffi

            • Nataschasays:

              Naja ich muss ja irgendwie ein Bild aufnehmen, aber mit dem Quelltext von dir wird ja nur die Webcam implementiert.. Und jetzt will ich ein Bild festhalten und in c# weiter bearbeiten.
              Der Sinn des RadioButtons hätte der sein sollen, dass die Webcam ein Bild aufnimmt wenn der RadioButton gedrückt wird. Aber vielleicht habe ich auch nur einen Gedankenfehler..
              Auf jeden Fall mal vielen Dank für deine Antworten – ich werde das schon irgendwie hinbekommen :)
              Lg Natascha

  11. christiansays:

    Vielen Dank. Das Beispiel hat mir sehr geholfen. Es wäre schön wenn eine Anleitung über Bilderkennung folgen würde.

    • Raffisays:

      Was genau möchtest du denn erkennen? Objekte? Bewegungen? Oder Inhalte, die ausgewertet werden sollen? (Sowas wie Texte z.B.)
      Und genügt es am Einzelbilder oder möchtest du das gerne am Livebild/Stream der Webcam umsetzen?

      Viele Grüße,
      Raffi

      • Martinsays:

        Hallo Raffi,
        ich bin auch noch neuling und suche nach einer lösung wie ich buchstaben von einem bild rausbekomme. z.B. von einem KFZ Kennzeichen

        gruß
        Martin

        • Hallo Martin,

          ich habe gerade nicht viel Zeit, deshalb erst mal kurz und knapp. Das Zauberwort heißt “OCR” (Optical Character Recognition). Ich hab mal schnell das Orakel befragt. Auf den ersten Blick scheint “Tesseract” wohl ganz geläufig. (Ich gehe mal davon aus, dass du erst mal eine kostenlose Lösung suchst.) Einen Beispiel Artikel zu Tesseract findest du hier: http://www.lexa-it.de/2013/04/code-tipp-ocr-funktionalitaet-fuer-ihre-c-anwendung/

          Testen konnte ich den Code auf die schnelle nicht. Wenn du ein wenig Geld über hast oder vielleicht eine Lizenz auf Halde, dann schau dir mal Abby Fine Reader an. Den hab ich vor Jahren auch mal mit C# zum Laufen gebracht. Die Ergebnisse waren ganz brauchbar.

          Viele Grüße

          • Hallo Raffi,
            danke für die Empfehlung meines Artikels. Ich hoffe, dass er für die Lösung dieses Anwendungsfalles hilfreich war ;).

  12. Nilssays:

    Hey ho, hab da mal ne Frage. Ich würd gerne AForge für die Bilderkennung nutzen, hab aber leider keine Ahnung wie ich ran gehen soll. Habe bereits ein paar .dlls eingeladen und versuche nun deren Funktione zu nutzen. Ich muss dazu sagen, dass ich fast noch ein Neuling auf diesem Gebiet bin … Realisiert habe ich das Ganze schon auf Matlab. Aber mit C# tue ich mich gerade echt schwer.

    Vielen Dank für die Hilfe

    • Raffisays:

      Hast du es geschafft meine Anleitung umzusetzen? Eigentlich sollte das klappen, wenn du sie Schritt für Schritt befolgst.

      Wenn es dir um das einbinden generell geht. Binde die dlls so ein, wie ich es hier im Post beschrieben habe. Um dann deren Funktionen zu nutzen, tippe einfach mal den Namen der Dll. Das Intellisense vom Visual Studio sollte dir da behilflich sein. Nachdem du den richten Namen getippt hast, mache einen Punkt dahinter. Nun solltest du alle Klassen/Methoden sehen, die diese DLL enthält und auf die du zugreifen kannst.

      Wenn dir das immer noch nicht weiter hilft, könnte ich dir noch anbieten, dass du mir den entsprechenden Quellcode-Ausschnitt mal zukommen lässt. Dann werfe ich mal einen Blick drauf.

      Ansonsten ist http://www.mycsharp.de auch immer eine gute Anlaufstelle bei Fragen rund um C#. ;)

      Viele Grüße,
      Raffi

Schreibe einen Kommentar zu Thomas Antworten abbrechen

Deine E-Mail-Adresse wird nicht veröffentlicht. Sie dient nur dem Spamschutz.