Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extract Frames from Video C#

I'm trying to make an app that use the camera to record a video and process the images of the video. Here is what I want. First, my app records a 10 second video with Torch. Second, I use a method to playback the video to see what I record.

I'm stuck on three things.

  1. How can I convert my video into individual frames (images)?
  2. Is it possible to asynchronously convert the video while it is being recorded?
  3. When I do convert the video into individual frames, how do I work with them? Are they JPEGs? Can I simply display them as images? Etc.

Main code:

using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

using Windows.UI.Xaml.Navigation;

namespace App3
{

public sealed partial class MainPage : Page
{          
    DispatcherTimer D;
    double basetimer = 0;
    public MainPage()
    {
        this.InitializeComponent();       
        this.NavigationCacheMode = NavigationCacheMode.Required;
        D = new DispatcherTimer();      
        D.Interval = new TimeSpan(0, 0, 1);
        D.Tick += timer_Tick;
        txt.Text = basetimer.ToString();
        Play.IsEnabled = false;            
    }  
    public Library Library = new Library();
    public object PreviewImage { get; private set; }
    void timer_Tick(object sender, object e)
    {
        basetimer = basetimer - 1;
        txt.Text = basetimer.ToString();
        if (basetimer == 0)
        {
            D.Stop();               
            Preview.Source = null;
            Library.Stop();
            Record.IsEnabled = false;
            Play.IsEnabled = true;
            Clear.IsEnabled = true;
            if (Library._tc.Enabled)
            {
                Library._tc.Enabled = false;
            }                
        }
    }
    private void Record_Click(object sender, RoutedEventArgs e)
    {            
        if (Library.Recording)
        {
            Preview.Source = null;
            Library.Stop();
            Record.Icon = new SymbolIcon(Symbol.Video);                
        }
        else
        {
            basetimer = 11;
            D.Start();
            //D.Tick += timer_Tick;
            Display.Source = null;
            Library.Record(Preview);
            Record.Icon = new SymbolIcon(Symbol.VideoChat);
            Record.IsEnabled = false;
            Play.IsEnabled = false;
        }
    }
    private async void Play_Click(object sender, RoutedEventArgs e)
    {            
        await Library.Play(Dispatcher, Display);
        //Extract_Image_From_Video(Library.buffer);            
    }
    private  void Clear_Click(object sender, RoutedEventArgs e)
    {
        Display.Source = null;            
        Record.Icon = new SymbolIcon(Symbol.Video);
        txt.Text = "0";
        basetimer=  0;
        Play.IsEnabled = false;
        Record.IsEnabled =true;
        if (Library.capture != null)
        {
            D.Stop();
            Library.Recording = false;
            Preview.Source = null;               
            Library.capture.Dispose();
            Library.capture = null;
            basetimer = 11;
        }
        }
    }
}

Library Class:

using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Windows.Devices.Enumeration;
using Windows.Media.Capture;
using Windows.Media.Devices;
using Windows.Media.MediaProperties;
using Windows.Storage;
using Windows.Storage.Streams;
using Windows.UI.Core;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Imaging;
using Windows.Graphics.Imaging;
using Emgu.CV.Structure;
using Emgu.CV;
using System.Collections.Generic;

public class Library
{

private const string videoFilename = "video.mp4";
private string filename;
public MediaCapture capture;
public InMemoryRandomAccessStream buffer;
public static bool Recording;
public TorchControl _tc;
public int basetimer  ;   
public async Task<bool> init()
{
    if (buffer != null)
    {
        buffer.Dispose();
    }
    buffer = new InMemoryRandomAccessStream();
    if (capture != null)
    {
        capture.Dispose();
    }
    try
    {

        if (capture == null)
        {
            var allVideoDevices = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture);               
            DeviceInformation cameraDevice =
            allVideoDevices.FirstOrDefault(x => x.EnclosureLocation != null &&
            x.EnclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Back);
            capture = new MediaCapture();
            var mediaInitSettings = new MediaCaptureInitializationSettings { VideoDeviceId = cameraDevice.Id };
            // Initialize 
            try
            {
                await capture.InitializeAsync(mediaInitSettings);
                var videoDev = capture.VideoDeviceController;
                _tc = videoDev.TorchControl;
                Recording = false;
                _tc.Enabled = false;                                      
            }
            catch (UnauthorizedAccessException)
            {
                Debug.WriteLine("UnauthorizedAccessExeption>>");
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Exception when initializing MediaCapture with {0}: {1}", cameraDevice.Id, ex.ToString());
            }
        }
            capture.Failed += (MediaCapture sender, MediaCaptureFailedEventArgs errorEventArgs) =>
        {
            Recording = false;
            _tc.Enabled = false;
            throw new Exception(string.Format("Code: {0}. {1}", errorEventArgs.Code, errorEventArgs.Message));
        };
    }
    catch (Exception ex)
    {
        if (ex.InnerException != null && ex.InnerException.GetType() == typeof(UnauthorizedAccessException))
        {
            throw ex.InnerException;
        }
        throw;
    }
    return true;
}
public async void Record(CaptureElement preview)
{    
    await init();
    preview.Source = capture; 
    await capture.StartPreviewAsync();
    await capture.StartRecordToStreamAsync(MediaEncodingProfile.CreateMp4(VideoEncodingQuality.Auto), buffer);
    if (Recording) throw new InvalidOperationException("cannot excute two records at the same time");
    Recording = true;
    _tc.Enabled = true;

}
public async void Stop()
{
    await capture.StopRecordAsync();
    Recording = false;
    _tc.Enabled = false;       
}    

public async Task Play(CoreDispatcher dispatcher, MediaElement playback)
{
    IRandomAccessStream video = buffer.CloneStream();

    if (video == null) throw new ArgumentNullException("buffer");
    StorageFolder storageFolder = Windows.ApplicationModel.Package.Current.InstalledLocation;
    if (!string.IsNullOrEmpty(filename))
    {
        StorageFile original = await storageFolder.GetFileAsync(filename);
        await original.DeleteAsync();
    }
    await dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
    {
        StorageFile storageFile = await storageFolder.CreateFileAsync(videoFilename, CreationCollisionOption.GenerateUniqueName);
        filename = storageFile.Name;
        using (IRandomAccessStream fileStream = await storageFile.OpenAsync(FileAccessMode.ReadWrite))
        {
            await RandomAccessStream.CopyAndCloseAsync(video.GetInputStreamAt(0), fileStream.GetOutputStreamAt(0));
            await video.FlushAsync();
            video.Dispose();
        }
        IRandomAccessStream stream = await storageFile.OpenAsync(FileAccessMode.Read);

        playback.SetSource(stream, storageFile.FileType);
        playback.Play();





    });


}   
like image 765
Ahmed Avatar asked Feb 13 '16 14:02

Ahmed


2 Answers

I ended up using MediaToolkit to solve a similar problem after having a ton of trouble with Accord.

I needed to save an image for every second of a video:

using (var engine = new Engine())
{
    var mp4 = new MediaFile { Filename = mp4FilePath };

    engine.GetMetadata(mp4);

    var i = 0;
    while (i < mp4.Metadata.Duration.Seconds)
    {
        var options = new ConversionOptions { Seek = TimeSpan.FromSeconds(i) };
        var outputFile = new MediaFile { Filename = string.Format("{0}\\image-{1}.jpeg", outputPath, i) };
        engine.GetThumbnail(mp4, outputFile, options);
        i++;
    }
}

Hope this helps someone some day.

UPDATE for .NET 5:

Recently, I have needed to update this code to work in .NET 5. To do so, I am using MediaToolkit.NetCore, which has been in preview for over a year. Also note: you will need to make the latest ffmpeg, including all 3 .exe files (ffmpeg, ffplay, ffprobe) available to your app.

Without further ado, here is the updated code:

// _env is the injected IWebHostEnvironment
// _tempPath is temporary file storage
var ffmpegPath = Path.Combine(_env.ContentRootPath, "<path-to-ffmpeg.exe>");

var mediaToolkitService = MediaToolkitService.CreateInstance(ffmpegPath);
var metadataTask = new FfTaskGetMetadata(_tempFile);
var metadata = await mediaToolkitService.ExecuteAsync(metadataTask);

var i = 0;
while (i < metadata.Metadata.Streams.First().DurationTs)
{
    var outputFile = string.Format("{0}\\image-{1:0000}.jpeg", _imageDir, i);
    var thumbTask = new FfTaskSaveThumbnail(_tempFile, outputFile, TimeSpan.FromSeconds(i));
    _ = await mediaToolkitService.ExecuteAsync(thumbTask);
    i++;
}
like image 61
Brendan Kendrick Avatar answered Sep 18 '22 11:09

Brendan Kendrick


I figured this out just yesterday.

Here is full and easy to understand example with picking video file and saving snapshot in 1st second of video.

You can take parts that fits your project and change some of them (i.e. getting video resolution from camera)

1) and 3)

TimeSpan timeOfFrame = new TimeSpan(0, 0, 1);

        //pick mp4 file
        var picker = new Windows.Storage.Pickers.FileOpenPicker();
        picker.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.VideosLibrary;
        picker.FileTypeFilter.Add(".mp4");
        StorageFile pickedFile = await picker.PickSingleFileAsync();
        if (pickedFile == null)
        {
            return;
        }
        ///


        //Get video resolution
        List<string> encodingPropertiesToRetrieve = new List<string>();
        encodingPropertiesToRetrieve.Add("System.Video.FrameHeight");
        encodingPropertiesToRetrieve.Add("System.Video.FrameWidth");
        IDictionary<string, object> encodingProperties = await pickedFile.Properties.RetrievePropertiesAsync(encodingPropertiesToRetrieve);
        uint frameHeight = (uint)encodingProperties["System.Video.FrameHeight"];
        uint frameWidth = (uint)encodingProperties["System.Video.FrameWidth"];
        ///


        //Use Windows.Media.Editing to get ImageStream
        var clip = await MediaClip.CreateFromFileAsync(pickedFile);
        var composition = new MediaComposition();
        composition.Clips.Add(clip);

        var imageStream = await composition.GetThumbnailAsync(timeOfFrame, (int)frameWidth, (int)frameHeight, VideoFramePrecision.NearestFrame);
        ///


        //generate bitmap 
        var writableBitmap = new WriteableBitmap((int)frameWidth, (int)frameHeight);
        writableBitmap.SetSource(imageStream);


        //generate some random name for file in PicturesLibrary
        var saveAsTarget = await KnownFolders.PicturesLibrary.CreateFileAsync("IMG" + Guid.NewGuid().ToString().Substring(0, 4) + ".jpg");


        //get stream from bitmap
        Stream stream = writableBitmap.PixelBuffer.AsStream();
        byte[] pixels = new byte[(uint)stream.Length];
        await stream.ReadAsync(pixels, 0, pixels.Length);

        using (var writeStream = await saveAsTarget.OpenAsync(FileAccessMode.ReadWrite))
        {
            var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, writeStream);
            encoder.SetPixelData(
                BitmapPixelFormat.Bgra8,
                BitmapAlphaMode.Premultiplied,
                (uint)writableBitmap.PixelWidth,
                (uint)writableBitmap.PixelHeight,
                96,
                96,
                pixels);
            await encoder.FlushAsync();

            using (var outputStream = writeStream.GetOutputStreamAt(0))
            {
                await outputStream.FlushAsync();
            }
        }

If you want to display frames in xaml Image, you should use imageStream

BitmapImage bitmapImage = new BitmapImage();
bitmapImage.SetSource(imageStream);

XAMLImage.Source = bitmapImage;

If you want to extract more frames, there is also composition.GetThumbnailsAsync

2) Use your mediaCapture, when your timer is ticking

like image 42
Alamakanambra Avatar answered Sep 18 '22 11:09

Alamakanambra