Display Webcam Feed in WPF

C# WPF
Display Webcam Feed in WPF

I wanted to display my webcam feed in a WPF App. But although the below code is fairly simple, finding a good, recently updated and working library for this purpose and getting it installed took much more time than expected.
So for this reason I put it on my website so you don't have to go through the same struggle.
The app can also scan the webcam feed for a QR Code or other types and act as a barcode scanner if needed.

The library I ended up using is Emgu CV. You need to install three Emgu packages and the ZXing package for reading QR codes from NuGet into your project.

To install these packages you may need to right click on the file 'packages.config' in Visual Studio and select 'Migrate packages.config to PackageReference' if you are getting errors during installation.
See Emgu Issues on GitHub for more information.

Furthermore there was an issue with a missing library named 'cvextern.dll'. It was not added to the project initially. So it is included in the source files just to be sure.
It is the 64 bit version. So make sure you set the platform target to x64 in 'Properties > Build > Platform target' in your project.

View source on GitHub Download the App

Code Snippets

The XAML of the MainWindow.

<Window x:Class="CaptureWebcam.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="Capture Webcam Feed" 
        Height="380" 
        Width="370"
        Background="#566895">
    <StackPanel>

        <Image Name="feedImage" Grid.Row="0" Grid.Column="0" Stretch="Fill" />
        <Image Name="Image1" Grid.Row="0" Grid.Column="0" Stretch="Fill" Visibility="Collapsed" />

        <TextBlock Name="TextBlock1" Grid.Row="1" Grid.Column="0" Foreground="White" Margin="10, 20, 10, 0" 
                HorizontalAlignment="Center" TextAlignment="Center" FontWeight="Bold" FontSize="12" />
        <TextBlock Name="TextBlock2" Grid.Row="1" Grid.Column="0" Foreground="White" Margin="10, 5, 10, 10" 
                HorizontalAlignment="Center" TextAlignment="Center" FontSize="10" />

    </StackPanel>
</Window>

And the MainWindow C# code to make it all work.

using System;
using System.Windows;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Timers;
using System.Windows.Media.Imaging;
using System.Threading.Tasks;
using System.Media;
using System.Reflection;
using ZXing;
using ZXing.Common;
using Emgu.CV;
using Emgu.CV.Structure;
using Emgu.CV.CvEnum;

namespace CaptureWebcam
{
    public partial class MainWindow : Window
    {
        VideoCapture capture;
        Timer timer;

        public MainWindow()
        {
            InitializeComponent();

            //the fps of the webcam
            int cameraFps = 30;

            //init the camera
            capture = new VideoCapture();

            //set the captured frame width and height (default 640x480)
            capture.Set(CapProp.FrameWidth, 1024);
            capture.Set(CapProp.FrameHeight, 768);

            //create a timer that refreshes the webcam feed
            timer = new Timer()
            {
                Interval = 1000 / cameraFps,
                Enabled = true
            };
            timer.Elapsed += new ElapsedEventHandler(timer_Tick);
        }


        private async void timer_Tick(object sender, ElapsedEventArgs e)
        {
            //there is a qr code image visible
            if (feedImage.Visibility == Visibility.Collapsed)
            {
                timer.Stop();

                //the delay time you want to display the qr code in the ui for
                await Task.Run(() => Task.Delay(2500));

                //set the image visibility
                this.Dispatcher.Invoke(() =>
                {
                    feedImage.Visibility = Visibility.Visible;
                    Image1.Visibility = Visibility.Collapsed;
                });

                timer.Start();
            }

            this.Dispatcher.Invoke(() =>
            {
                var mat1 = capture.QueryFrame();
                var mat2 = new Mat();

                //flip the image horizontally
                CvInvoke.Flip(mat1, mat2, FlipType.Horizontal);

                //convert the mat to a bitmap
                var bmp = mat2.ToImage().ToBitmap();

                //copy the bitmap to a memorystream
                var ms = new MemoryStream();
                bmp.Save(ms, ImageFormat.Bmp);

                //display the image on the ui
                feedImage.Source = BitmapFrame.Create(ms);

                //try to find a qr code in the feed
                string qrcode = FindQrCodeInImage(bmp);

                if (!string.IsNullOrEmpty(qrcode))
                {
                    //set the found text in the qr code in the ui
                    TextBlock1.Text = qrcode;
                    TextBlock2.Text = $"Last scan: {DateTime.Now.ToLongDateString()} at {DateTime.Now.ToShortTimeString()}.";

                    //play a sound to indicate qr code found
                    var player_ok = new SoundPlayer(GetStreamFromResource("sound_ok.wav"));
                    player_ok.Play();

                    //hide the feed image
                    feedImage.Visibility = Visibility.Collapsed;
                }
            });
        }


        private string FindQrCodeInImage(Bitmap bmp)
        {
            //decode the bitmap and try to find a qr code
            var source = new BitmapLuminanceSource(bmp);
            var bitmap = new BinaryBitmap(new HybridBinarizer(source));
            var result = new MultiFormatReader().decode(bitmap);

            //no qr code found in bitmap
            if (result == null)
            {
                return null;
            }

            //create a new qr code image
            var writer = new BarcodeWriter
            {
                Format = BarcodeFormat.QR_CODE,
                Options = new EncodingOptions
                {
                    Height = 300,
                    Width = 300
                }
            };

            //write the result to the new qr code bmp image
            var qrcode = writer.Write(result.Text);

            //make the bmp transparent
            qrcode.MakeTransparent();

            //show the found qr code in the app
            var stream = new MemoryStream();
            qrcode.Save(stream, ImageFormat.Png);

            //display the new qr code in the ui
            Image1.Source = BitmapFrame.Create(stream);
            Image1.Visibility = Visibility.Visible;

            //and/or save the new qr code image to disk if needed
            try
            {
                //qrcode.Save($"qr_code_{DateTime.Now.ToString("yyyyMMddHHmmss")}.gif", ImageFormat.Gif);
            }
            catch
            {
                //handle disk write errors here
            }

            //return the found qr code text
            return result.Text;
        }


        private static Stream GetStreamFromResource(string filename)
        {
            var assembly = Assembly.GetExecutingAssembly();
            return assembly.GetManifestResourceStream(string.Format("{0}.Resources.{1}", assembly.GetName().Name, filename));
        }
    }
}