OVERVIEW
In Part 1, you have learned what OpenCV is, what is the role of Emgu CV wrapper and how to create a Visual Studio 2017 C# project that utilizes the two libraries. In this part, I will show you how to loop through frames captured from video file. Check the first part to watch demo video and find information about sample project (all the interesting stuff is inside Program.cs - keep this file opened in separate tab as its fragments will be shown in this post)...
STEP 1: Capturing Video From File
Before any processing can happen, we need to obtain a frame from video file. This can be easily done by using VideoCapture class (many tutorials mention Capture
class instead but it is not available in recent Emgu versions).
Check the Main
method from our sample project:
private const string BackgroundFrameWindowName = "Background Frame";
private static Mat backgroundFrame = new Mat();
static void Main(string[] args)
{
string videoFile = @"PUT A PATH TO VIDEO FILE HERE!";
using (var capture = new VideoCapture(videoFile))
{
if (capture.IsOpened)
{
backgroundFrame = capture.QueryFrame();
CvInvoke.Imshow(BackgroundFrameWindowName, backgroundFrame);
VideoProcessingLoop(capture, backgroundFrame);
}
else
{
Console.WriteLine($"Unable to open {videoFile}");
}
}
}
VideoCapture
has four constructor versions. The overload we are using takes string
parameter that is a path to video file or video stream. Other versions allow us to connect to cameras. If you design your program right, switching from file input to a webcam might be as easy as changing new VideoCapture
call!
Once VideoCapture
instance is created, we can confirm if opening went fine by accessing IsOpened
property (maybe path is wrong or codecs are missing?).
VideoCapture
offers few ways of acquiring frames but the one I find most convenient is by call to QueryFrame method. This method returns Mat
class instance (you know it already from part 1) and moves to next frame. If next frame cannot be found, then null
is returned. We can use this fact to easily loop through video.
STEP 2: Loading and Presenting Background Frame
Our drone detection project is based on finding the difference between background frame and other frames. The assumption is that we can treat the first frame obtained from the video as the background, hence the call to QueryFrame
right after creating VideoCapture
object:
backgroundFrame = capture.QueryFrame();
After background is loaded, we can check how it looks with a call to Imshow
method (you know it from part 1 too):
CvInvoke.Imshow(BackgroundFrameWindowName, backgroundFrame);
Is finding a (meaningful!) difference in a video always as easy as subtracting frames? No, it isn't. First of all, the background might not be static
(imagine that drone was flying in front of threes moved by wind or if lighting in a room was changing significantly). The second challenge might come from movements of the camera. Having a fixed background and camera position keeps our drone detection task simple enough for beginner's OpenCV tutorial plus it's not completely unrealistic. Video detection/recognition is often used in fully controlled environment such as part of factory... OpenCV is capable of handling more complex scenarios - you can read about background subtraction techniques and optical flow to get a hint...
STEP 3: Looping Through Video Frames
We know that we can use QueryFrame
to get a single frame image (Mat
instance) and progress to next frame and we know that QueryFrame
returns null
if it can't go any further. Let's use this knowledge to build a method that goes through frames in a loop:
private static void VideoProcessingLoop(VideoCapture capture, Mat backgroundFrame)
{
var stopwatch = new Stopwatch();
int frameNumber = 1;
while (true)
{
rawFrame = capture.QueryFrame();
if (rawFrame != null)
{
frameNumber++;
stopwatch.Restart();
ProcessFrame(backgroundFrame, Threshold, ErodeIterations, DilateIterations);
stopwatch.Stop();
WriteFrameInfo(stopwatch.ElapsedMilliseconds, frameNumber);
ShowWindowsWithImageProcessingStages();
int key = CvInvoke.WaitKey(0);
if (key == 27)
Environment.Exit(0);
}
else
{
capture.SetCaptureProperty(CapProp.PosFrames, 0);
frameNumber = 0;
}
}
}
In each loop iteration, a frame is grabbed from video file. It is then passed to ProcessFrame
method which does image difference, noise removal, contour detection and drawing (it will be discussed in detail in the next post)... Call to ProcessFrame
is surrounded with System.Diagnostics.Stopwatch
usage - this way, we can measure video processing performance. It took my laptop only about 1.5ms to fully handle each frame - I've told you OpenCV is fast! :)
If QueryFrame
returns null
, then program moves back to first frame by calling SetCaptureProperty
method on VideoCapture
instance (video will be processed again).
WriteFrameInfo
puts a text in the frame's upper-left corner with information about its number and how long it took to process it. ShowWindowsWithImageProcessingStages
ensures that we can see current (raw) frame, background frame, intermediate frames and final frame in separate windows... Both methods will be shown in the next post.
The while
loop is going to spin forever unless program execution is stopped by Escape key being pressed in any of the windows that show frames (not the console window!). If 0
is passed as WaitKey
argument, then program waits until some key is pressed. This lets you look at each frame as long as you want. If you pass other number to WaitKey
, then the program will wait until key is pressed or a delay elapses. You might use it to automatically play video at specified frame rate:
int fps = (int)capture.GetCaptureProperty(CapProp.Fps);
int key = CvInvoke.WaitKey(1000 / fps);
Warning: One thing you might notice while processing videos is that moving through a file is not always as easy as setting CapProp.PosFrame
to desired number. Your experience might vary from format to format. This is because video files are optimized for playing forward at natural speed and frames might not be simply kept as sequence of images. Full HD (1920x1080) movie has over 2 million pixels in each frame. Now let's say we have an hour of video at 30 FPS -> 3600 * 30 * 2,073,600 = 223,948,800,000. Independent frame compression is not enough to crush that number! No wonder some people need to dedicate their scientific/software careers to video compression...
Ok, enough for now - next part coming soon!
Update: Part 3 is ready!