Preview webcam video and take snapshot in WPF using AForge and MVVM with sample

Shout out: WPF programmer, someday you will be asked to work on a feature where you need to show live preview of a attached USB camera (webcam) and on click of button you need to capture a picture and save or display in some image control.

Pretty simple right?

Well actually not, ok partially. Fine its pretty simple. Let me walk you through it and save you some time.

People who wants to jump right into the code refer / download attached sample at the end of this article. Best of luck !!

How to preview webcam (camera) video and take snapshot with WPF using AForge and MVVM.

Step:
Create a new WPF project or use your ongoing / existing WPF project, whatever you fancy.

Step:

Add reference to AForge NuGet package that will do the "talking to the webcam" stuffs for us.

  • AForge.Controls
  • AForge.Video.DirectShow

By adding this two packages will automatically add dependent packages. Also add reference to MVVM Light toolkit (if you have created a new project).

Note: AForge is very mature and robust opensource library for audio / video processing / rendering and manipulation stuffs. Go for it thing and I am not affiliated with them by any means.
Shout out to Glenn Fransen (past colleague and ninga VC++ / Embedded developer) for AForge suggestion, thanks :)

Step: Next add reference to two assemblies System.Windows.Forms and WindowsFormsIntegration. This will be used to host the video preview for selected video device source.

Step: Create new WPF user control (XAML) file that will contain the video preview and conditionally display optional text / frame conveying user message like "No video source found" or something similar.

The XAML for this user control would be as below:

<UserControl x:Class="TakeSnapsWithWebcamUsingWpfMvvm.Video.WebcamDevice"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:controls="clr-namespace:AForge.Controls;assembly=AForge.Controls"
             mc:Ignorable="d"
             d:DesignHeight="300"
             d:DesignWidth="300"
             Loaded="WebcamDeviceOnLoaded"
             Unloaded="WebcamDeviceOnUnloaded">
    <Grid>
        <Grid x:Name="NoVideoSourceGrid"
              Margin="10">
            <Border BorderBrush="DimGray"
                    BorderThickness="1">
                <TextBlock x:Name="NoVideoSourceMessage"
                           VerticalAlignment="Center"
                           HorizontalAlignment="Center"
                           TextWrapping="Wrap"
                           FontSize="20"
                           FontWeight="Bold" />
            </Border>
        </Grid>
        <WindowsFormsHost x:Name="VideoSourceWindowsFormsHost"
                          Margin="10"
                          Background="Transparent">
            <controls:VideoSourcePlayer x:Name="VideoSourcePlayer" />
        </WindowsFormsHost>
    </Grid>
</UserControl>

Step: In code behind of the user control - Create five dependency properties that will control the video preview's:

  • Width (Video preview width)
  • Height (Video preview height)
  • VideoSourceId (USB moniker string - something like "@device:pnp:\?\usb#vid_098d&pid_0542&mi_02#7&65f95301&0&0057#{87e9643d-ab56-11d0-a3b9-00a0c9223196}\global")
  • SnapshotBitmap (Bitmap instance of currently rendering frame from USB camera)
  • TakeSnapshot (implementation for ICommand to take snapshot from currently rendering frame)

Also contains implementation for callback on value changed for this dependency properties.

Step: Following code snippet will provide list of device (video input device only) attached to the machine (via USB).

public static IEnumerable<MediaInformation> GetVideoDevices
{
    get
    {
        var filterVideoDeviceCollection = new FilterInfoCollection(FilterCategory.VideoInputDevice);
        return (from FilterInfo filterInfo in filterVideoDeviceCollection select new MediaInformation { DisplayName = filterInfo.Name, UsbId = filterInfo.MonikerString }).ToList();
    }
}

Step: Import "AttachedCommandBehavior" into your project. I have adapted the same into the sample reffereed at the end of this post. This is proposed in one of the blog post of MARLONGRECH here.

Why should you use it, you ask?

It is not a mandatory thing, if you wish to tweak the implementation. But the way I made it work. I was in need to attach more than one command binding to the button where - when the user clicks on "Take snapshot" button, first command will grab the currently rendered SnapshotBitmap instance from preview and second command will take than instance and display into a Image control on screen.

Step: Go ahead and create your UI where you can add a ComboBox to display list of attached video devices. On select one - the preview control can start rendering the live preview from the selected video source. On click of "Take snapshot" button it would take a snap and display in a Image control.

This code snippet demonstrates use of AttachedCommandBehavior in the MainWindow.xaml of the sample:

<TextBlock Grid.Column="0"
            Grid.Row="0"
            Text="Select Video Device Source: "
            Margin="20" />
<ComboBox Grid.Column="1"
            Grid.Row="0"
            ItemsSource="{Binding MediaDeviceList}"
            DisplayMemberPath="DisplayName"
            SelectedValuePath="UsbString"
            SelectedItem="{Binding SelectedVideoDevice}"
            Width="200"
            HorizontalAlignment="Left"
            VerticalAlignment="Center"
            Margin="0,20,20,20" />
<TextBlock Grid.Column="0"
            Grid.Row="1"
            Text="Selected Device Preview:"
            Margin="20,20,20,5" />
<video:WebcamDevice Grid.Row="2"
                    HorizontalAlignment="Left"
                    VerticalAlignment="Top"
                    Grid.Column="0"
                    Grid.ColumnSpan="2"
                    Margin="20,0,20,20"
                    x:Name="CameraVideoDeviceControl"
                    VideoPreviewWidth="{Binding VideoPreviewWidth}"
                    VideoPreviewHeight="{Binding VideoPreviewHeight}"
                    VideoSourceId="{Binding SelectedVideoDevice, Converter={StaticResource MediaInformationConverter}}"
                    SnapshotBitmap="{Binding SnapshotBitmap, Mode=TwoWay}" />
<Button Grid.Row="3"
        Margin="20,20,20,5"
        Grid.Column="0"
        Content="Take Snapshot">
    <cmdBehavior:CommandBehaviorCollection.Behaviors>
        <cmdBehavior:BehaviorBinding Event="Click"
                                        Command="{Binding TakeSnapshot, ElementName=CameraVideoDeviceControl}" />
        <cmdBehavior:BehaviorBinding Event="Click"
                                        Command="{Binding SnapshotCommand}" />
    </cmdBehavior:CommandBehaviorCollection.Behaviors>
</Button>
    <Image Source="{Binding SnapshotTaken}"
            HorizontalAlignment="Left"
            Grid.Row="4"
            Grid.Column="0"
            Grid.ColumnSpan="2"
            VerticalAlignment="Top"
            Width="320"
            Height="240"
            Margin="20,0,20,20" />

Screenshot of the sample while running:

Above narrated / demonstrated sample can be cloned / forked @ Github.

Download Code

Update 26th Jan 2015:

I feel glad that what I share is actually helpful to my fellow developers and come back with question / queries or request for further changes - enhancments.

In above code and the source code @ Github - has the User control WebcamDevice using custom dependency property for setting the Width and Height of the Video device preview. This initially only accepted integer values and no Auto as value to inherit Width and Height of its parent control.

Chris Ni came up with this query / request to allow Auto for those dependency properties VideoPreviewWidth and VideoPreviewHeight. So I made further modification to support the same.

For assigning Auto as value to work i added following on the dependency property's getter / setter property:

[TypeConverter(typeof(LengthConverter))]

LengthConverter - TypeConverter does the trick. This didn't worked for the Height and Width set with type int so I changed it to double and also added few checks in the PropertyCoherceValueChanged event handler for Height and Width property and handle double.NaN - equivalent of Auto based on LengthConverter. The value for double.NaN is also handled while taking snapshot in TakeSnapshotCallback method and use ActualWidth and ActualHeight values when value is set as Auto.

Feel free to comeback with any issues / bugs you may encounter in code, happy to help :)

Happy Coding!!