Compare picture in Word file and in a folder? - c#

So I'm using Word.Interloop and in order to compare two pics, I guess I have to transform the current picture(in word file) to a bitmap image and then compare it with a bitmap image object from desktop?
Or perhaps the is a simpler way to do so?
Word.InlineShape x;
x.isEqual( Picture from Desktop/ bitmapImage.Object);

I have made a small sample showing how this can be accomplished. The main idea is to represent your image from your desktop as a Bitmap instance and then compare it pixel by pixel to the Bitmap instance in your document. The comparison is done by first copying an inline shape to the clipboard, then turning it into a Bitmap, and then compare it with the reference (from the desktop) - first by size and then pixel by pixel.
The sample is implemented as a C# console application using .NET 4.5, Microsoft Office Object Library version 15.0, and Microsoft Word Object Library version 15.0.
using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
using Application = Microsoft.Office.Interop.Word.Application;
namespace WordDocStats
{
class Program
{
// General idea is based on: https://stackoverflow.com/a/7937590/700926
static void Main()
{
// Open a doc file
var wordApplication = new Application();
var document = wordApplication.Documents.Open(#"C:\Users\Username\Documents\document.docx");
// Load the image to compare against.
var bitmapToCompareAgainst = new Bitmap(#"C:\Users\Username\Documents\image.png");
// For each inline shape, do a comparison
// By inspection you can see that the first inline shape have index 1 ( and not zero as one might expect )
for (var i = 1; i <= wordApplication.ActiveDocument.InlineShapes.Count; i++)
{
// closure
// http://confluence.jetbrains.net/display/ReSharper/Access+to+modified+closure
var inlineShapeId = i;
// parameterized thread start
// https://stackoverflow.com/a/1195915/700926
var thread = new Thread(() => CompareInlineShapeAndBitmap(inlineShapeId, bitmapToCompareAgainst, wordApplication));
// STA is needed in order to access the clipboard
// https://stackoverflow.com/a/518724/700926
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
}
// Close word
wordApplication.Quit();
Console.ReadLine();
}
// General idea is based on: https://stackoverflow.com/a/7937590/700926
protected static void CompareInlineShapeAndBitmap(int inlineShapeId, Bitmap bitmapToCompareAgainst, Application wordApplication)
{
// Get the shape, select, and copy it to the clipboard
var inlineShape = wordApplication.ActiveDocument.InlineShapes[inlineShapeId];
inlineShape.Select();
wordApplication.Selection.Copy();
// Check data is in the clipboard
if (Clipboard.GetDataObject() != null)
{
var data = Clipboard.GetDataObject();
// Check if the data conforms to a bitmap format
if (data != null && data.GetDataPresent(DataFormats.Bitmap))
{
// Fetch the image and convert it to a Bitmap
var image = (Image)data.GetData(DataFormats.Bitmap, true);
var currentBitmap = new Bitmap(image);
var imagesAreEqual = true;
// Compare the images - first by size and then pixel by pixel
// Based on: http://www.c-sharpcorner.com/uploadfile/prathore/image-comparison-using-C-Sharp/
if(currentBitmap.Width == bitmapToCompareAgainst.Width && currentBitmap.Height == bitmapToCompareAgainst.Height)
{
for (var i = 0; i < currentBitmap.Width; i++)
{
if(!imagesAreEqual)
break;
for (var j = 0; j < currentBitmap.Height; j++)
{
if (currentBitmap.GetPixel(i, j).Equals(bitmapToCompareAgainst.GetPixel(i, j)))
continue;
imagesAreEqual = false;
break;
}
}
}
else
{
imagesAreEqual = false;
}
Console.WriteLine("Inline shape #{0} is equal to the 'external' bitmap: {1}", inlineShapeId, imagesAreEqual);
}
else
{
Console.WriteLine("Clipboard data is not in an image format");
}
}
else
{
Console.WriteLine("Clipboard is empty");
}
}
}
}
References:
Threadstart with params: https://stackoverflow.com/a/1195915/700926
Extracting inline shapes as images from word in C#: https://stackoverflow.com/a/7937590/700926
Comparing images in C#:
http://www.c-sharpcorner.com/uploadfile/prathore/image-comparison-using-C-Sharp/
Details on how to retrieve an image from the clipboard in C#: https://stackoverflow.com/a/998825/700926
Details on how to access the clipboard from C#: https://stackoverflow.com/a/518724/700926

Related

How to convert an array of images in C# to a one dimension vector?

I have a folder that contains 200 images. I loaded all these images into an array list of type image called training. I have a problem converting this to a one dimension vector. I need help with this, am writing a PCA based solution for face recognition, Thank You.
List<Image> training = new List<Image>();
//the path to the images
static string path = "C:/Users/User/Documents/visual studio 2015/Projects/PCA/PCA/training";
public Form1()
{
InitializeComponent();
}
//the method below starts the training process
private void button1_Click(object sender, EventArgs e)
{
/*read all the images in the training folder into an array
in this case the images are already in gray scale so we do not need to convert
*/
var files = Directory.GetFiles(path);
foreach(string r in files)
{
if(Regex.IsMatch(r, #"\.jpg$|\.png$|\.gif$"))
{
training.Add(Image.FromFile(r));
}
}
//convert the list of images to a one dimension vector
}
Update
I have a variable called matrix data which has been initialized to size [200,92*112], and then I have this list training, I need to loop through all the images in the list and access a pixel and assign that to the matrix_data. I think am now clear, how do achieve this?
First of all, I'm not sure what you mean when you say "vector". You could be talking about C#'s Vector<T> (documentation here), or you could be talking about a vector as in a 1D array. If it's the latter a C# array will do the trick and most of my answer will still be helpful.
I am going to assume you want a Vector<T>, because its the more difficult option and is useful for parallelization, which you will probably take advantage of in neural network training.
To put all your pixels into a Vector<T>, your first instinct will probably be to use Vector<System.Drawing.Color>. This is a perfectly reasonable approach, but it won't work, because Vector<T> only works for C#'s built-in numbers (byte, int, float, etc.). So we will have to come up with a workaround.
Thankfully a Color only has 4 components that we care about, each of which can be expressed as a single byte: alpha, red, blue, and green. That means we can store the information of a Color inside any 4 byte number. I chose to use uint:
private static uint ToUint(Color color)
{
var bytes = new[] { color.A,color.R,color.G,color.B};
return BitConverter.ToUInt32(bytes);
}
Converting back to a color is just as easy:
private static Color ToColor(uint integer)
{
var bytes = BitConverter.GetBytes(integer);
return Color.FromArgb(bytes[0], bytes[1], bytes[2], bytes[3]);
}
Now all we have to do is iterate through the pixels, convert them to uints, store them in an array, and make a vector from the array:
private static Vector<uint> ConvertImagesToColorVector(IEnumerable<Bitmap> images)
{
var pixelCount = images.Sum(image => image.Width * image.Height);
var pixels = new uint[pixelCount];
var index = 0;
foreach (var image in images)
{
foreach (var pixel in GetPixels(image))
{
pixels[index++] = ToUint(pixel);
}
}
return new Vector<uint>(pixels);
}
private static IEnumerable<Color> GetPixels(Bitmap bitmap)
{
for (var row = 0; row < bitmap.Height; row++)
{
for (var column = 0; column < bitmap.Width; column++)
{
yield return bitmap.GetPixel(column, row);
}
}
}
Here's what this looks like in your code:
private void button1_Click(object sender, EventArgs e)
{
/*read all the images in the training folder into an array
in this case the images are already in gray scale so we do not need to convert
*/
var files = Directory.GetFiles(path);
var imageFiles = new List<string>();
foreach (string r in files)
{
if (Regex.IsMatch(r, #"\.jpg$|\.png$|\.gif$"))
{
training.Add(Image.FromFile(r));
}
}
var vector = ConvertImagesToColorVector(training.Select(i=>new Bitmap(i)));
//do whatever you want with the vector here. If you need to convert back to a color use ToColor
}
Why not something like this:
public static IEnumerable<System.Drawing.Color> ColorEnumerable(System.Drawing.Image picture)
{
System.Drawing.Bitmap btmp = new Bitmap(picture);
//TODO maybe LockBits first
for(int i = 0; i < btmp.Height; i++)
{
for(int j = 0; j < btmp.Width; j++)
{
yield return btmp.GetPixel(i,j);
}
}
}
which you can use to assign to the matrix in your code as you like:
foreach(Image image in training)
{
if(Regex.IsMatch(r, #"\.jpg$|\.png$|\.gif$"))
{
training.Add(Image.FromFile(r));
// you could even populate your matrix here
}
}
//convert the list of images to a one dimension vector
System.Numeric.Vector<Color> colorVector;
for(int i = 0; i < training.Count(); i++)
{
colorVector = new Vector(ColorEnumerable(training[i]).ToArray());
// TODO add colorVector to your matrix or whatever
}

How to Stream string data from a txt file into an array

I'm doing this exercise from a lab. the instructions are as follows
This method should read the product catalog from a text file called “catalog.txt” that you should
create alongside your project. Each product should be on a separate line.Use the instructions in the video to create the file and add it to your project, and to return an
array with the first 200 lines from the file (use the StreamReader class and a while loop to read
from the file). If the file has more than 200 lines, ignore them. If the file has less than 200 lines,
it’s OK if some of the array elements are empty (null).
I don't understand how to stream data into the string array any clarification would be greatly appreciated!!
static string[] ReadCatalogFromFile()
{
//create instance of the catalog.txt
StreamReader readCatalog = new StreamReader("catalog.txt");
//store the information in this array
string[] storeCatalog = new string[200];
int i = 0;
//test and store the array information
while (storeCatalog != null)
{
//store each string in the elements of the array?
storeCatalog[i] = readCatalog.ReadLine();
i = i + 1;
if (storeCatalog != null)
{
//test to see if its properly stored
Console.WriteLine(storeCatalog[i]);
}
}
readCatalog.Close();
Console.ReadLine();
return storeCatalog;
}
Here are some hints:
int i = 0;
This needs to be outside your loop (now it is reset to 0 each time).
In your while() you should check the result of readCatalog() and/or the maximum number of lines to read (i.e. the size of your array)
Thus: if you reached the end of the file -> stop - or if your array is full -> stop.
static string[] ReadCatalogFromFile()
{
var lines = new string[200];
using (var reader = new StreamReader("catalog.txt"))
for (var i = 0; i < 200 && !reader.EndOfStream; i++)
lines[i] = reader.ReadLine();
return lines;
}
A for-loop is used when you know the exact number of iterations beforehand. So you can say it should iterate exactly 200 time so you won't cross the index boundaries. At the moment you just check that your array isn't null, which it will never be.
using(var readCatalog = new StreamReader("catalog.txt"))
{
string[] storeCatalog = new string[200];
for(int i = 0; i<200; i++)
{
string temp = readCatalog.ReadLine();
if(temp != null)
storeCatalog[i] = temp;
else
break;
}
return storeCatalog;
}
As soon as there are no more lines in the file, temp will be null and the loop will be stopped by the break.
I suggest you use your disposable resources (like any stream) in a using statement. After the operations in the braces, the resource will automatically get disposed.

How can i convert a Png file to Gif in memory?

This is the code:
if (Form1.sf != null)
{
radarDI = new DirectoryInfo(Form1.sf);
radarFiles = radarDI.GetFiles("*.png");
for (int i = radarFiles.Length - 1; i >= 0; i--)
{
radarImage = Image.FromFile(radarFiles[i].Name);
radarImage.Save(FileName, System.Drawing.Imaging.ImageFormat.Gif);
myGifListRadar.Add(radarFiles[i].Name);
if (myGifListRadar.Count == 5)
{
break;
}
}
radar_animation.MakeGIF(myGifListRadar, #"d:\RadarGifAnimatoion.gif", 8, true);
}
I want to convert all the png files to gif.
Since I need to create animated gif and it's getting only gif files.
So now im saving the png files to the hard disk make the convertion and then reading back the gif files from the hard disk:
radarImage.Save(FileName, System.Drawing.Imaging.ImageFormat.Gif);
FileName should be later on the files im saving and then reading back to use it.
The question is if there is any way to convert the files to gif without saving it to the hard disk first ? Some quicker way then saving the files to the hard disk convert then read them again as gif's with FileInfo again ?
Just updated:
using (var ms = new MemoryStream())
{
if (Form1.sf != null)
{
radarDI = new DirectoryInfo(Form1.sf);
radarFiles = radarDI.GetFiles("*.png");
for (int i = radarFiles.Length - 1; i >= 0; i--)
{
radarImage = Image.FromFile(radarFiles[i].FullName);
radarImage.Save(ms, System.Drawing.Imaging.ImageFormat.Gif);
myGifListRadar.Add(radarFiles[i].Name);
if (myGifListRadar.Count == 5)
{
break;
}
}
}
radar_animation.MakeGIF(myGifListRadar, #"d:\RadarGifAnimatoion.gif", 8, true);
}
But now this part:
radarImage.Save(ms, System.Drawing.Imaging.ImageFormat.Gif);
myGifListRadar.Add(radarFiles[i].Name);
myGifListRadar is List how can I add now the radarImage to the List as string file name ?
Use Image.Save(Stream, ImageFormat):
using (var ms = new MemoryStream)
{
radarImage.Save(ms, System.Drawing.Imaging.ImageFormat.Gif);
// do something with ms
}
Depending on how you are making a GIF with multiple frames, this might be unnecessary - can you just take the separate Images and make the multi-frame GIF directly instead of making separate GIFs and then merging them?

Multi threading with each thread run sequentially to make multiple tiff image with unique file name

I am currently working on project where the application take files from folder and reach each image for barcode. and when it reads the barcode it push all the non-barcode files in array and that array of files will be merged with multiple page single tiff Image with unique name in destination.
which all i done and its working but i want to make it fast using Threads.
i have function like -
read image files till it reach barcode.
make a unique name in destination folder
make multiple page single tiff image
save it in destination foler.
to read Barcode i use simple Barcode reader where i get Image have barcode or no.and rest i m doing with array in foreach loop. some code is here
foreach (string path in filenames_1)
{
Image bmp = Bitmap.FromFile(path);
Bitmap bn = (Bitmap)bmp;
readimage(bn);
if (s == 1) //If it is Bookmarked Content Found Then s=1 else its s=0
{
if (z == 0) // i used z coz in my process first file in directory is barcode image so first time have to skip it so i set z=1 at begining and second time it become 0 and process continues
{
j = 3; continue;
MakeUnique("c:\\sachin.tiff");
ConvertToMultiPageTiff(fileNames, fnamem);
fileNames.Clear();
}
fileNames.Add(path);
j = 1;
if (z == 0)
{
j = 3;
}
else
{
}
}
else
{
if (j == 1) // j==1 means its regular tiff file wher i make them add to string array
{
fileNames.Add(path); // string array with files to be made multiple tiff image become single mulipage tiff image
j = 1;
z = 0;
}
if (j == 3)
{
z = 1;
j = 1;
fileNames.Add(path);
MakeUnique("c:\\sachin.tiff");
ConvertToMultiPageTiff(fileNames, fnamem); // this function converts all the added files in filearray of single tiff image to multiple page single tiff image
fileNames.Clear();
}
else
{
}
}
}
}
MakeUnique("c:\\sachin.tiff");
ConvertToMultiPageTiff(fileNames, fnamem);
fileNames.Clear();
}
try this logic
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Net;
using System.Collections;
using System.Threading;
namespace ConsoleApplication4
{
static class Program
{
private static ManualResetEvent wh;
private static int total;
private static int done;
private static void Main()
{
string[] myfiles = new string[] {"file1", "file2"};
wh = new ManualResetEvent(false);
total = myfiles.Length;
foreach (string myfile in myfiles)
{
ThreadPool.QueueUserWorkItem(ProcessImageFile, myfile);
}
wh.WaitOne(Timeout.Infinite);
//wohoo all files are process now, at faster rate;
}
private static void ProcessImageFile(object state)
{
string file = state as string;
// process the file here
Interlocked.Increment(ref done);
if (done == total)
{
wh.Set();
}
}
}
}

Image sequence to video stream?

Like many people already seem to have (there are several threads on this subject here) I am looking for ways to create video from a sequence of images.
I want to implement my functionality in C#!
Here is what I wan't to do:
/*Pseudo code*/
void CreateVideo(List<Image> imageSequence, long durationOfEachImageMs, string outputVideoFileName, string outputFormat)
{
// Info: imageSequence.Count will be > 30 000 images
// Info: durationOfEachImageMs will be < 300 ms
if (outputFormat = "mpeg")
{
}
else if (outputFormat = "avi")
{
}
else
{
}
//Save video file do disk
}
I know there's a project called Splicer (http://splicer.codeplex.com/) but I can't find suitable documentation or clear examples that I can follow (these are the examples that I found).
The closest I want to do, which I find here on CodePlex is this:
How can I create a video from a directory of images in C#?
I have also read a few threads about ffmpeg (for example this: C# and FFmpeg preferably without shell commands? and this: convert image sequence using ffmpeg) but I find no one to help me with my problem and I don't think ffmpeg-command-line-style is the best solution for me (because of the amount of images).
I believe that I can use the Splicer-project in some way (?).
In my case, it is about about > 30 000 images where each image should be displayed for about 200 ms (in the videostream that I want to create).
(What the video is about? Plants growing ...)
Can anyone help me complete my function?
Well, this answer comes a bit late, but since I have noticed some activity with my original question lately (and the fact that there was not provided a working solution) I would like to give you what finally worked for me.
I'll split my answer into three parts:
Background
Problem
Solution
Background
(this section is not important for the solution)
My original problem was that I had a lot of images (i.e. a huge amount), images that were individually stored in a database as byte arrays. I wanted to make a video sequence with all these images.
My equipment setup was something like this general drawing:
The images depicted growing tomato plants in different states. All images were taken every 1 minute under daytime.
/*pseudo code for taking and storing images*/
while (true)
{
if (daylight)
{
//get an image from the camera
//store the image as byte array to db
}
//wait 1 min
}
I had a very simple db for storing images, there were only one table (the table ImageSet) in it:
Problem
I had read many articles about ffmpeg (please see my original question) but I couldn't find any on how to go from a collection of images to a video.
Solution
Finally, I got a working solution!
The main part of it comes from the open source project AForge.NET. In short, you could say that AForge.NET is a computer vision and artificial intelligence library in C#.
(If you want a copy of the framework, just grab it from http://www.aforgenet.com/)
In AForge.NET, there is this VideoFileWriter class (a class for writing videofiles with help of ffmpeg). This did almost all of the work. (There is also a very good example here)
This is the final class (reduced) which I used to fetch and convert image data into a video from my image database:
public class MovieMaker
{
public void Start()
{
var startDate = DateTime.Parse("12 Mar 2012");
var endDate = DateTime.Parse("13 Aug 2012");
CreateMovie(startDate, endDate);
}
/*THIS CODE BLOCK IS COPIED*/
public Bitmap ToBitmap(byte[] byteArrayIn)
{
var ms = new System.IO.MemoryStream(byteArrayIn);
var returnImage = System.Drawing.Image.FromStream(ms);
var bitmap = new System.Drawing.Bitmap(returnImage);
return bitmap;
}
public Bitmap ReduceBitmap(Bitmap original, int reducedWidth, int reducedHeight)
{
var reduced = new Bitmap(reducedWidth, reducedHeight);
using (var dc = Graphics.FromImage(reduced))
{
// you might want to change properties like
dc.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
dc.DrawImage(original, new Rectangle(0, 0, reducedWidth, reducedHeight), new Rectangle(0, 0, original.Width, original.Height), GraphicsUnit.Pixel);
}
return reduced;
}
/*END OF COPIED CODE BLOCK*/
private void CreateMovie(DateTime startDate, DateTime endDate)
{
int width = 320;
int height = 240;
var framRate = 200;
using (var container = new ImageEntitiesContainer())
{
//a LINQ-query for getting the desired images
var query = from d in container.ImageSet
where d.Date >= startDate && d.Date <= endDate
select d;
// create instance of video writer
using (var vFWriter = new VideoFileWriter())
{
// create new video file
vFWriter.Open("nameOfMyVideoFile.avi", width, height, framRate, VideoCodec.Raw);
var imageEntities = query.ToList();
//loop throught all images in the collection
foreach (var imageEntity in imageEntities)
{
//what's the current image data?
var imageByteArray = imageEntity.Data;
var bmp = ToBitmap(imageByteArray);
var bmpReduced = ReduceBitmap(bmp, width, height);
vFWriter.WriteVideoFrame(bmpReduced);
}
vFWriter.Close();
}
}
}
}
Update 2013-11-29 (how to) (Hope this is what you asked for #Kiquenet?)
Download AForge.NET Framework from the downloads page (Download full ZIP archive and you will find many interesting Visual Studio solutions with projects, like Video, in the AForge.NET Framework-2.2.5\Samples folder...)
Namespace: AForge.Video.FFMPEG (from the documentation)
Assembly: AForge.Video.FFMPEG (in AForge.Video.FFMPEG.dll) (from the documentation) (you can find this AForge.Video.FFMPEG.dll in the AForge.NET Framework-2.2.5\Release folder)
If you want to create your own solution, make sure you have a reference to AForge.Video.FFMPEG.dll in your project. Then it should be easy to use the VideoFileWriter class. If you follow the link to the class you will find a very good (and simple example). In the code, they are feeding the VideoFileWriter with Bitmap image in a for-loop
I found this code in the slicer samples, looks pretty close to to what you want:
string outputFile = "FadeBetweenImages.wmv";
using (ITimeline timeline = new DefaultTimeline())
{
IGroup group = timeline.AddVideoGroup(32, 160, 100);
ITrack videoTrack = group.AddTrack();
IClip clip1 = videoTrack.AddImage("image1.jpg", 0, 2); // play first image for a little while
IClip clip2 = videoTrack.AddImage("image2.jpg", 0, 2); // and the next
IClip clip3 = videoTrack.AddImage("image3.jpg", 0, 2); // and finally the last
IClip clip4 = videoTrack.AddImage("image4.jpg", 0, 2); // and finally the last
}
double halfDuration = 0.5;
// fade out and back in
group.AddTransition(clip2.Offset - halfDuration, halfDuration, StandardTransitions.CreateFade(), true);
group.AddTransition(clip2.Offset, halfDuration, StandardTransitions.CreateFade(), false);
// again
group.AddTransition(clip3.Offset - halfDuration, halfDuration, StandardTransitions.CreateFade(), true);
group.AddTransition(clip3.Offset, halfDuration, StandardTransitions.CreateFade(), false);
// and again
group.AddTransition(clip4.Offset - halfDuration, halfDuration, StandardTransitions.CreateFade(), true);
group.AddTransition(clip4.Offset, halfDuration, StandardTransitions.CreateFade(), false);
// add some audio
ITrack audioTrack = timeline.AddAudioGroup().AddTrack();
IClip audio =
audioTrack.AddAudio("testinput.wav", 0, videoTrack.Duration);
// create an audio envelope effect, this will:
// fade the audio from 0% to 100% in 1 second.
// play at full volume until 1 second before the end of the track
// fade back out to 0% volume
audioTrack.AddEffect(0, audio.Duration,
StandardEffects.CreateAudioEnvelope(1.0, 1.0, 1.0, audio.Duration));
// render our slideshow out to a windows media file
using (
IRenderer renderer =
new WindowsMediaRenderer(timeline, outputFile, WindowsMediaProfiles.HighQualityVideo))
{
renderer.Render();
}
}
I could not manage to get the above example to work. However I did find another library that works amazingly well once. Try via NuGet "accord.extensions.imaging.io", then I wrote the following little function:
private void makeAvi(string imageInputfolderName, string outVideoFileName, float fps = 12.0f, string imgSearchPattern = "*.png")
{ // reads all images in folder
VideoWriter w = new VideoWriter(outVideoFileName,
new Accord.Extensions.Size(480, 640), fps, true);
Accord.Extensions.Imaging.ImageDirectoryReader ir =
new ImageDirectoryReader(imageInputfolderName, imgSearchPattern);
while (ir.Position < ir.Length)
{
IImage i = ir.Read();
w.Write(i);
}
w.Close();
}
It reads all images from a folder and makes a video out of them.
If you want to make it nicer you could probably read the image dimensions instead of hard coding, but you got the point.
The FFMediaToolkit is a good solution in 2020, with .NET Core support.
https://github.com/radek-k/FFMediaToolkit
FFMediaToolkit is a cross-platform .NET Standard library for creating and reading video files. It uses native FFmpeg libraries by the FFmpeg.Autogen bindings.
The README of the library has a nice example for the question asked.
// You can set there codec, bitrate, frame rate and many other options.
var settings = new VideoEncoderSettings(width: 1920, height: 1080, framerate: 30, codec: VideoCodec.H264);
settings.EncoderPreset = EncoderPreset.Fast;
settings.CRF = 17;
var file = MediaBuilder.CreateContainer(#"C:\videos\example.mp4").WithVideo(settings).Create();
while(file.Video.FramesCount < 300)
{
file.Video.AddFrame(/*Your code*/);
}
file.Dispose(); // MediaOutput ("file" variable) must be disposed when encoding is completed. You can use `using() { }` block instead.
This is a solution for creating a video from an image sequence using Visual Studio using C#.
My starting point was "Hauns TM"'s answer below but my requirements were more basic than theirs so this solution might be more appropriated for less advanced users ( like myself )
Libraries:
using System;
using System.IO;
using System.Drawing;
using Accord.Video.FFMPEG;
You can get the FFMPEG libarary by searching for FFMPEG in "Tools -> NuGet Package Manager -> Manage NuGet Packages for a Solution..."
The variables that I passed into the function are:
outputFileName = "C://outputFolder//outputMovie.avi"
inputImageSequence =
["C://inputFolder//image_001.avi",
"C://inputFolder//image_002.avi",
"C://inputFolder//image_003.avi",
"C://inputFolder//image_004.avi"]
Function:
private void videoMaker( string outputFileName , string[] inputImageSequence)
{
int width = 1920;
int height = 1080;
var framRate = 25;
using (var vFWriter = new VideoFileWriter())
{
// create new video file
vFWriter.Open(outputFileName, width, height, framRate, VideoCodec.Raw);
foreach (var imageLocation in inputImageSequence)
{
Bitmap imageFrame = System.Drawing.Image.FromFile(imageLocation) as Bitmap;
vFWriter.WriteVideoFrame(imageFrame);
}
vFWriter.Close();
}
}
It looks like many of these answers are a bit obsolete year 2020, so I add my thoughts.
I have been working on the same problem and have published the .NET Core project Time Lapse Creator on GitHub: https://github.com/pekspro/TimeLapseCreator It shows how to add information on extra frame (like a timestamp for instance), background audio, title screen, fading and some more. And then ffmpeg is used to make the rendering. This is done in this function:
// Render video from a list of images, add background audio and a thumbnail image.
private async Task RenderVideoAsync(int framesPerSecond, List<string> images, string ffmpgPath,
string audioPath, string thumbnailImagePath, string outPath,
double videoFadeInDuration = 0, double videoFadeOutDuration = 0,
double audioFadeInDuration = 0, double audioFadeOutDuration = 0)
{
string fileListName = Path.Combine(OutputPath, "framelist.txt");
var fileListContent = images.Select(a => $"file '{a}'{Environment.NewLine}duration 1");
await File.WriteAllLinesAsync(fileListName, fileListContent);
TimeSpan vidLengthCalc = TimeSpan.FromSeconds(images.Count / ((double)framesPerSecond));
int coverId = -1;
int audioId = -1;
int framesId = 0;
int nextId = 1;
StringBuilder inputParameters = new StringBuilder();
StringBuilder outputParameters = new StringBuilder();
inputParameters.Append($"-r {framesPerSecond} -f concat -safe 0 -i {fileListName} ");
outputParameters.Append($"-map {framesId} ");
if(videoFadeInDuration > 0 || videoFadeOutDuration > 0)
{
List<string> videoFilterList = new List<string>();
if (videoFadeInDuration > 0)
{
//Assume we fade in from first second.
videoFilterList.Add($"fade=in:start_time={0}s:duration={videoFadeInDuration.ToString("0", NumberFormatInfo.InvariantInfo)}s");
}
if (videoFadeOutDuration > 0)
{
//Assume we fade out to last second.
videoFilterList.Add($"fade=out:start_time={(vidLengthCalc.TotalSeconds - videoFadeOutDuration).ToString("0.000", NumberFormatInfo.InvariantInfo)}s:duration={videoFadeOutDuration.ToString("0.000", NumberFormatInfo.InvariantInfo)}s");
}
string videoFilterString = string.Join(',', videoFilterList);
outputParameters.Append($"-filter:v:{framesId} \"{videoFilterString}\" ");
}
if (thumbnailImagePath != null)
{
coverId = nextId;
nextId++;
inputParameters.Append($"-i {thumbnailImagePath} ");
outputParameters.Append($"-map {coverId} ");
outputParameters.Append($"-c:v:{coverId} copy -disposition:v:{coverId} attached_pic ");
}
if (audioPath != null)
{
audioId = nextId;
nextId++;
inputParameters.Append($"-i {audioPath} ");
outputParameters.Append($"-map {audioId} ");
if(audioFadeInDuration <= 0 && audioFadeOutDuration <= 0)
{
// If no audio fading, just copy as it is.
outputParameters.Append($"-c:a copy ");
}
else
{
List<string> audioEffectList = new List<string>();
if(audioFadeInDuration > 0)
{
//Assume we fade in from first second.
audioEffectList.Add($"afade=in:start_time={0}s:duration={audioFadeInDuration.ToString("0", NumberFormatInfo.InvariantInfo)}s");
}
if (audioFadeOutDuration > 0)
{
//Assume we fade out to last second.
audioEffectList.Add($"afade=out:start_time={(vidLengthCalc.TotalSeconds - audioFadeOutDuration).ToString("0.000", NumberFormatInfo.InvariantInfo)}s:duration={audioFadeOutDuration.ToString("0.000", NumberFormatInfo.InvariantInfo)}s");
}
string audioFilterString = string.Join(',', audioEffectList);
outputParameters.Append($"-filter:a \"{audioFilterString}\" ");
}
}
int milliseconds = vidLengthCalc.Milliseconds;
int seconds = vidLengthCalc.Seconds;
int minutes = vidLengthCalc.Minutes;
var hours = (int)vidLengthCalc.TotalHours;
string durationString = $"{hours:D}:{minutes:D2}:{seconds:D2}.{milliseconds:D3}";
outputParameters.Append($"-c:v:{framesId} libx264 -pix_fmt yuv420p -to {durationString} {outPath} -y ");
string parameters = inputParameters.ToString() + outputParameters.ToString();
try
{
await Task.Factory.StartNew(() =>
{
var outputLog = new List<string>();
using (var process = new Process
{
StartInfo =
{
FileName = ffmpgPath,
Arguments = parameters,
UseShellExecute = false,
CreateNoWindow = true,
// ffmpeg send everything to the error output, standard output is not used.
RedirectStandardError = true
},
EnableRaisingEvents = true
})
{
process.ErrorDataReceived += (sender, e) =>
{
if (string.IsNullOrEmpty(e.Data))
{
return;
}
outputLog.Add(e.Data.ToString());
Console.WriteLine(e.Data.ToString());
};
process.Start();
process.BeginErrorReadLine();
process.WaitForExit();
if (process.ExitCode != 0)
{
throw new Exception($"ffmpeg failed error exit code {process.ExitCode}. Log: {string.Join(Environment.NewLine, outputLog)}");
}
Console.WriteLine($"Exit code: {process.ExitCode}");
}
});
}
catch(Win32Exception )
{
Console.WriteLine("Oh no, failed to start ffmpeg. Have you downloaded and copied ffmpeg.exe to the output folder?");
}
Console.WriteLine();
Console.WriteLine("Video was successfully created. It is availible at: " + Path.GetFullPath(outPath));
}
This function is based on Splicer.Net library.Took me ages to understand how that library works.
Make sure that your fps(frame per second )is correct. By the way standard 24 f/s.
In my case I have 15 images and I now that I need 7 seconds video-> so fps =2.
Fps may vary according to platform...or developer usage.
public bool CreateVideo(List<Bitmap> bitmaps, string outputFile, double fps)
{
int width = 640;
int height = 480;
if (bitmaps == null || bitmaps.Count == 0) return false;
try
{
using (ITimeline timeline = new DefaultTimeline(fps))
{
IGroup group = timeline.AddVideoGroup(32, width, height);
ITrack videoTrack = group.AddTrack();
int i = 0;
double miniDuration = 1.0 / fps;
foreach (var bmp in bitmaps)
{
IClip clip = videoTrack.AddImage(bmp, 0, i * miniDuration, (i + 1) * miniDuration);
System.Diagnostics.Debug.WriteLine(++i);
}
timeline.AddAudioGroup();
IRenderer renderer = new WindowsMediaRenderer(timeline, outputFile, WindowsMediaProfiles.HighQualityVideo);
renderer.Render();
}
}
catch { return false; }
return true;
}
Hope this helps.

Categories

Resources