W oparciu o aplikację, którą przedstawiałem w poprzednim wpisie, zademonstruję jak napisać aplikację wykrywającą twarz przy pomocy biblioteki Emgu CV.
Cały kod bezpośrednio związany z wykrywaniem twarzy można znaleźć w przykładach dostarczonych wraz z biblioteką.
Do projektu poprzedniej aplikacji dodaję jedynie nowy serwis „FaceDetectionService”, który dziedziczy po klasie WebCamService.
Mój pomysł polega na tym, aby rozszerzyć klasę WebCamService o dodatkowy event,
public event ImageWithDetectionChangedEventHandler ImageWithDetectionChanged; public delegate void ImageWithDetectionChangedEventHandler(object sender, Image<Bgr, Byte> image);
który ma być „rozszerzeniem” na istniejący już istniejący „ImageChanged”.
Różnica polega na tym, że przed podniesieniem zdarzenia, zdjęcie przekazywane w parametrze jest dodatkowo obrabiane – rysowane są na nim prostokąty w miejscu wykrycia twarzy.
private void InitializeServices() { base.ImageChanged += _webCamService_ImageChanged; } private void RaiseImageWithDetectionChangedEvent(Image<Bgr, Byte> image) { if (ImageWithDetectionChanged != null) { ImageWithDetectionChanged(this, image); } } private bool isDetecting = false; private async void _webCamService_ImageChanged(object sender, Image<Bgr, byte> image) { bool isDelayed = false; if (!isDetecting) { isDetecting = true; var result = await DetectFacesAsync(image); isDelayed = true; _faces = result; isDetecting = false; } if (!isDelayed)// to prevent displaing delayed image { DrawRectangles(image); RaiseImageWithDetectionChangedEvent(image); } }
Funkcja wykrywania twarzy (lekko zmodyfikowana wersja funkcji dostarczonej wraz z przykładami):
private void DetectFace(Image<Bgr, Byte> image, List<Rectangle> faces) { #if !IOS if (GpuInvoke.HasCuda) { using (GpuCascadeClassifier face = new GpuCascadeClassifier(_faceFileName)) { using (GpuImage<Bgr, Byte> gpuImage = new GpuImage<Bgr, byte>(image)) using (GpuImage<Gray, Byte> gpuGray = gpuImage.Convert<Gray, Byte>()) { Rectangle[] faceRegion = face.DetectMultiScale(gpuGray, 1.1, 10, Size.Empty); faces.AddRange(faceRegion); } } } else #endif { //Read the HaarCascade objects using (CascadeClassifier face = new CascadeClassifier(_faceFileName)) { using (Image<Gray, Byte> gray = image.Convert<Gray, Byte>()) //Convert it to Grayscale { //normalizes brightness and increases contrast of the image gray._EqualizeHist(); //Detect the faces from the gray scale image and store the locations as rectangle //The first dimensional is the channel //The second dimension is the index of the rectangle in the specific channel Rectangle[] facesDetected = face.DetectMultiScale( gray, 1.1, 10, new Size(20, 20), Size.Empty); faces.AddRange(facesDetected); } } } }
Metoda w wersji asynchronicznej:
private Task<List<Rectangle>> DetectFacesAsync(Image<Bgr, byte> image) { return Task.Run(() => { List<Rectangle> faces = new List<Rectangle>(); DetectFace(image, faces); return faces; }); }
Cały kod jak zawsze dostępny na GITHUB 😉
Efekt na zdjęciu:
oraz filmie:
Brawo. Gratulacje dla autora
[…] Tworzymy nowy projekt WPF! (w tym przykładzie będę opierał się sporo na projekcie z mojego poprzedniego wpisu) […]