C# Emgu CV Gesichtserkennung

In diesem Beitrag zeige ich euch wie man in Visual Studio mit C# und Emgu CV eine Gesichtserkennungssoftware programmieren kann.

Dieses Programm kann nicht nur Gesichter erkennen, sondern es kann auch Gesichter wiedererkennen. Dieses wird oftmals verwechselt, aber es sind zwei unterschiedliche Dinge. So können am Ende mit dem Programm Gesichter erkannt werden und mit einem Namen versehen werden, damit sie danach vom Programm wiedererkannt werden.

Wenn ihr keine Zeit oder Lust habt das ganze Projekt nach zu programmieren, dann könnt ihr das fertige Projekt hier herunterladen: Win_FaceDetection.zip

Wichtig!!! Die Emgu.CV.UI.dll und Emgu.CV.World.dll müssen beim Öffnen und Bearbeiten des Projektes als Verweise neu hinzugefügt werden.

Als erstes müssen wir die neuste Version des Emgu CV Projekts als ZIP-Datei herunterladen:

Emgu CV

Als nächstes entpacken wir die ZIP-Datei auf dem Desktop. Nun erstellen wir ein neues Projekt in Visual Studio. Nach dem Erstellen des Projektes müssen nun einige Dateien von dem Emgu CV Projekt in unser Projekt hinzugefügt werden. Alle Dateien die in den nachfolgenden Schritten hinzugefügt werden, befinden sich in dem entpacktem Emgu CV Projekt auf dem Desktop.

Als erstes werden zwei DLL-Dateien als Verweise in unser Projekt mit aufgenommen.

  • bin\Emgu.CV.UI.dll
  • bin\Emgu.CV.World.dll

Um die DLLs als Verweise hinzuzufügen einfach auf “Projekt” > “Verweise hinzufügen…” klicken.

Als nächstes werden weitere DLLs und eine Klasse aus dem Emgu CV Projekt in unser Projekt hinzugefügt. Folgende DLLs werden hinzugefügt:

  • \libs\x86\concrt140.dll
  • \libs\x86\cvextern.dll
  • \libs\x86\msvcp140.dll
  • \libs\x86\opencv_ffmpeg341.dll
  • \libs\x86\vcruntime140.dll
  • \Emgu.CV.Contrib\Face\Recognizer.cs

Um Elemente in ein Projekt hinzuzufügen einfach auf “Projekt” >  “Vorhandenes Element hinzufügen…” klicken.

Als letztes wird noch eine XML-Datei zum Projekt hinzugefügt. Diese dient als Datenbank, um nachher Gesichter zu erkennen.

  • \opencv\data\haarcascades\haarcascade_frontalface_default.xml

Für diese Datei erstellen wir einen neuen Ordner im Projektmappen-Explorer. Dazu einfach auf  “Projekt” >  “Neuer Ordner” klicken. Der Name des Ordners lautet: “data“. In diesem Ordner wird nun die “haarcascade_frontalface_default.xml” hinzugefügt. Alle bei allen hinzugefügten Dateien (außer den Verweisen) muss die Eigenschaft “In Ausgabeverzeichnis kopieren” auf “Immer kopieren” eingestellt werden.

So sieht es aus wenn alle Dateien zum Projekt hinzugefügt wurden:

Um die Klasse “Recognizer.cs” und deren Methoden verwenden zu können, muss der “namespace” der Klasse geändert werden. Als Namespace wird dein Projektname eigetragen. So wird der Code in der Recognizer.cs von:

namespace EmguCVFace
{
    /// <summary>
    /// Face Recognizer
    /// </summary>
    public abstract class FaceRecognizer : UnmanagedObject
    {

in

namespace EmguCVFace
{
    /// <summary>
    /// Face Recognizer
    /// </summary>
    public abstract class FaceRecognizer : UnmanagedObject
    {

geändert. Als letzte Vorbereitung müsst ihr in eurem Projektordner einen neuen Ordner erstellen.

  • Win_FaceDetection\Win_FaceDetection\bin\Debug\Face Collection

In diesem Ordner erstellt ihr ein Testdokument mit dem Namen: “Datenbank“. Nun kann mit dem eigentlichen Programmieren begonnen werden ;). Ich zeige nun meinen ganzen Quellcode den ihr auch gerne in euren Projekt Optimieren und Weiterentwickeln könnt.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

using Emgu.CV;
using Emgu.CV.Structure;
using Emgu.CV.CvEnum;
using System.IO;

namespace Win_FaceDetection
{
    public partial class frmFaceDetection : Form
    {

        Image<Bgr, byte> img;
        VideoCapture vid;
        int indexofSelectedFace = 0;
        FaceRecognizer recognizer = new EigenFaceRecognizer(80, double.PositiveInfinity);
        Bitmap[] detectedFaces;
        string[] faceNames;
        int[] faceIDs;

        public frmFaceDetection()
        {
            InitializeComponent();

            stopToolStripMenuItem.Enabled = false;
            btnZurück.Enabled = false;
            btnVor.Enabled = false;
            btnVerwerfen.Enabled = false;
            btnHinzufügen.Enabled = false;
        }  
        
        private void drawText(Image<Bgr, Byte> img, Rectangle rect, string text)
        {
            Font font = new Font("Arial", 12, FontStyle.Bold);
            Graphics g = Graphics.FromImage(img.Bitmap);

            int tWidth = (int)g.MeasureString(text, font).Width;
            int x;
            if (tWidth >= rect.Width)
                x = rect.Left - ((tWidth - rect.Width) / 2);
            else
                x = (rect.Width / 2) - (tWidth / 2) + rect.Left;

            g.DrawString(text, font, Brushes.Red, new PointF(x, rect.Top - 18));
        }

        public void GesichterErkennen()
        {
            Rectangle[] faceRectangles = null;
            detectedFaces = null;
            string facePath = Path.GetFullPath(@"data/haarcascade_frontalface_default.xml");
            CascadeClassifier classifierFace = new CascadeClassifier(facePath);
            var imgGray = img.Convert<Gray, byte>().Clone();
            faceRectangles = classifierFace.DetectMultiScale(imgGray, 1.1, 4);
            detectedFaces = new Bitmap[faceRectangles.Length];

            for (int i = 0; i < faceRectangles.Length; i++)
            {
                img.Draw(faceRectangles[i], new Bgr(255, 0, 0), 1);
                imgGray.ROI = faceRectangles[i];
                var detectedFace = imgGray.Copy();
                detectedFaces[i] = detectedFace.Resize(200, 200, Inter.Linear).ToBitmap();
            }
            pictureBoxInput.Image = img.ToBitmap();
        }

        public void GesichterWiedererkennen()
        {
            Rectangle[] faceRectangles = null;
            string facePath = Path.GetFullPath(@"data/haarcascade_frontalface_default.xml");
            CascadeClassifier classifierFace = new CascadeClassifier(facePath);
            var imgGray = img.Convert<Gray, byte>().Clone();
            faceRectangles = classifierFace.DetectMultiScale(imgGray, 1.1, 4);

            for (int i = 0; i < faceRectangles.Length; i++)
            {
                int foundfaceID = 0;
                imgGray.ROI = faceRectangles[i];
                var detectedFace = imgGray.Copy();
                detectedFace = detectedFace.Resize(200, 200, Inter.Linear);
                var result = recognizer.Predict(detectedFace);
                /*Die Dictance ist ein Wert, der angibt wie viel Ähnlichkeit das
                  aktuelle Gesicht mit allen anderen Gesichtern in der Datenbank
                  hat. Je kleiner der Wert desto ähnlicher ist das aktuelle Gesicht
                  zu einem anderem Gesicht in der Datenbank.*/
                var distance = result.Distance;
                foundfaceID = result.Label;

                if (distance < 3000)
                {
                    string foundfaceName = "";
                    for (int j = 0; j < faceIDs.Length; j++)
                    {
                        if (faceIDs[j] == foundfaceID)
                        {
                            foundfaceName = faceNames[j];
                            break;
                        }
                    }
                    /*Methode drawText wird aufgerufen um den Namen des 
                    wiedererkannten Gesichts in die PictureBox zu schreiben.*/
                    drawText(img, faceRectangles[i], foundfaceName);
                    img.Draw(faceRectangles[i], new Bgr(0, 255, 0), 1);
                }
            }
            pictureBoxInput2.Image = img.ToBitmap();
        }

        private void öffnenToolStripMenuItem_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Filter = "Image files (*.jpg, *.jpeg, *.jpe, *.jfif, *.png)" +
                " | *.jpg; *.jpeg; *.jpe; *.jfif; *.png";
            if (ofd.ShowDialog() == DialogResult.OK)
            {
                img = new Image<Bgr, byte>(ofd.FileName);
                int index = tabControl1.SelectedIndex;
                
                //"Wiedererkennen" Tab im Fokus
                if (index == 1)
                {
                    pictureBoxInput2.Image = img.ToBitmap();
                    Trainieren();
                    GesichterWiedererkennen();
                }
                //"Erkennen" Tab im Fokus
                else
                {
                    pictureBoxInput.Image = img.ToBitmap();
                    GesichterErkennen();
                }
            }  
        }

        private void startToolStripMenuItem_Click(object sender, EventArgs e)
        {

            startToolStripMenuItem.Enabled = false;
            öffnenToolStripMenuItem.Enabled = false;
            stopToolStripMenuItem.Enabled = true;

            try
            {
                vid = new VideoCapture();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }

            int index = tabControl1.SelectedIndex;
            //"Wiedererkennen" Tab im Fokus
            if(index == 1)
            {
                Trainieren();
                Application.Idle += ProcessFrameWiedererkennen;
            }
            //"Erkennen" Tab im Fokus
            else
            {
                Application.Idle += ProcessFrameErkennen;
            } 
        }

        private void ProcessFrameWiedererkennen(object sender, EventArgs e)
        {
            img = vid.QueryFrame().ToImage<Bgr, byte>();
            GesichterWiedererkennen();
        }

        private void ProcessFrameErkennen(object sender, EventArgs e)
        {
            img = vid.QueryFrame().ToImage<Bgr, byte>();
            GesichterErkennen();
        }

        private void stopToolStripMenuItem_Click(object sender, EventArgs e)
        {
            startToolStripMenuItem.Enabled = true;
            öffnenToolStripMenuItem.Enabled = true;
            stopToolStripMenuItem.Enabled = false;
            Application.Idle -= ProcessFrameWiedererkennen;
            Application.Idle -= ProcessFrameErkennen;
            vid.Dispose();         
        }

        private void tabControl1_SelectedIndexChanged(object sender, EventArgs e)
        {
            /*Wenn man bei einer laufenden Videoaufnahme den Tab wechselt, 
              wird die Videoaufnahme gestoppt.*/
            stopToolStripMenuItem.PerformClick();
        }

        private void btnExtrahieren_Click(object sender, EventArgs e)
        {
            if (pictureBoxInput.Image != null)
            {
                btnExtrahieren.Enabled = false;
                btnVerwerfen.Enabled = true;
                btnHinzufügen.Enabled = true;
                //Wenn Videostream noch läuft, wird dieser angehalten.
                stopToolStripMenuItem.PerformClick();
                /*Erstes erkanntes Gesicht wird in 
                  die kleine PictureBox geladen.*/
                if (detectedFaces.Length > 0)
                {
                    if (detectedFaces.Length >= 1)
                    {
                        btnVor.Enabled = true;
                    }
                    pictureBoxFace.Image = detectedFaces[0];
                }
            }    
        }

        private void btnVerwerfen_Click(object sender, EventArgs e)
        {
            /*Verworfenes Bild aus dem Bitmap Array entfernen und 
              das nächste Bild einblenden lassen.*/
            detectedFaces = detectedFaces.Skip(1).ToArray();
            textBoxName.Text = "";
            if (detectedFaces.Length-1 > 0)
            {              
                pictureBoxFace.Image = detectedFaces[0];
            }
            else if (detectedFaces.Length-1 == 0)
            {
                btnVor.Enabled = false;
                pictureBoxFace.Image = detectedFaces[0];
            }
            else
            {
                btnExtrahieren.Enabled = true;
                btnVerwerfen.Enabled = false;
                btnHinzufügen.Enabled = false;
                pictureBoxFace.Image = null;
                pictureBoxInput.Image = null;
            }
            
        }

        private void btnHinzufügen_Click(object sender, EventArgs e)
        {
            if (textBoxName.Text == "")
            {
                MessageBox.Show("Keinen Namen vergeben.");
            }
            else
            {
                //Abspeichern des ausgewählten Gesichts
                Random rand = new Random(DateTime.Now.Millisecond);
                int random = Math.Abs(rand.Next()*1000);
                pictureBoxFace.Image.Save(@"Face Collection\Face_" + 
                    (textBoxName.Text) +"_"+ random + ".bmp");
                textBoxName.Text = "";

                /*Abgespeichertes Bild aus dem Bitmap Array entfernen und 
                  das nächste Bild einblenden lassen.*/
                detectedFaces = detectedFaces.Skip(1).ToArray();
                if (detectedFaces.Length - 1 > 0)
                {
                    pictureBoxFace.Image = detectedFaces[0];
                }
                else if (detectedFaces.Length - 1 == 0)
                {
                    btnVor.Enabled = false;
                    pictureBoxFace.Image = detectedFaces[0];
                }
                else
                {
                    btnExtrahieren.Enabled = true;
                    btnVerwerfen.Enabled = false;
                    btnHinzufügen.Enabled = false;
                    pictureBoxFace.Image = null;
                    pictureBoxInput.Image = null;
                }
                
            }
        }

        private void btnZurück_Click(object sender, EventArgs e)
        {      
            indexofSelectedFace--;
            if (indexofSelectedFace == 0)
            {
                btnZurück.Enabled = false;
                btnHinzufügen.Enabled = true;
                btnVerwerfen.Enabled = true;
                pictureBoxFace.Image = detectedFaces[indexofSelectedFace];
            }
            else
            {
                btnVor.Enabled = true;
                pictureBoxFace.Image = detectedFaces[indexofSelectedFace];
            }
        }

        private void btnVor_Click(object sender, EventArgs e)
        {           
            indexofSelectedFace++;
            if (indexofSelectedFace == detectedFaces.Length-1)
            {
                btnVor.Enabled = false;
                pictureBoxFace.Image = detectedFaces[indexofSelectedFace];
            }
            else
            {
                btnZurück.Enabled = true;
                btnHinzufügen.Enabled = false;
                btnVerwerfen.Enabled = false;
                pictureBoxFace.Image = detectedFaces[indexofSelectedFace];
            }  
        }

        private void Trainieren()
        {
            string[] filePaths = Directory.GetFiles(@"Face Collection\", "*.bmp");
            var faceImages = new Image<Gray, byte>[filePaths.Count()];
            faceNames = new string[filePaths.Count()];
            faceIDs = new int[filePaths.Count()];
            if (filePaths.Count() == 0)
            {
                MessageBox.Show("Keine Bilder in der Datenbank vorhanden");
            }
            else
            {
                for (int i = 0; i < filePaths.Count(); i++)
                {
                    var faceImage = new Image<Gray, byte>(filePaths[i]);
                    string faceName = filePaths[i].Split('_')[1];

                    char[] charsToTrim = { '.', 'b', 'm', 'p' };
                    int faceID = Convert.ToInt32(filePaths[i].Split('_')[2].Trim(charsToTrim));

                    faceImages[i] = faceImage;
                    faceNames[i] = faceName;
                    faceIDs[i] = faceID;
                }
                /*Der EigenFaceRecognizer wird mit allen Bildern gefüttert 
                  die im Ordner "Face Collection" sind. Datenbank als Textdatei wird auch 
                  erstellt und danach wieder eingelesen.*/
                recognizer.Train(faceImages, faceIDs);
                recognizer.Write(@"Face Collection\database.txt");
                recognizer.Read(@"Face Collection\database.txt");
                MessageBox.Show("Algorithmus wurde mit den gespeicherten Bildern trainiert.");
            }
        }

    }
}