在本文中,将探讨如何使用Visual Studio2017和C#创建一个项目,该项目利用两个库来处理视频文件。将展示如何循环遍历从视频文件捕获的帧。请参考第一部分观看演示视频并找到关于示例项目的更多信息(所有有趣的内容都在Program.cs文件中 - 请保持此文件在单独的标签页中打开,因为本文将展示其片段)...
在任何处理之前,需要从视频文件中获取一帧。这可以通过使用VideoCapture类来轻松完成(许多教程提到Capture类,但在最近的Emgu版本中不可用)。检查示例项目中的Main方法:
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类有四个构造函数版本。使用的重载版本接受一个string参数,即视频文件或视频流的路径。其他版本允许连接到摄像头。如果正确设计程序,从文件输入切换到网络摄像头可能就像改变新的VideoCapture调用一样简单!
一旦创建了VideoCapture实例,可以通过访问IsOpened属性来确认是否成功打开(也许路径是错误的,或者缺少编解码器?)。VideoCapture提供了几种获取帧的方法,但发现最方便的方法是调用QueryFrame方法。此方法返回Mat类实例(已经从第一部分知道了),并移动到下一帧。如果找不到下一帧,则返回null。可以利用这个事实轻松地循环遍历视频。
无人机检测项目基于找到背景帧和其他帧之间的差异。假设可以将从视频中获得的第一帧视为背景,因此在创建VideoCapture对象后立即调用QueryFrame:
backgroundFrame = capture.QueryFrame();
加载背景后,可以通过调用Imshow方法(也知道它来自第一部分)来检查它的外观:
CvInvoke.Imshow(BackgroundFrameWindowName, backgroundFrame);
在视频中总是像减去帧一样容易找到(有意义的!)差异吗?不,不是的。首先,背景可能不是静态的(想象一下无人机在被风吹动的树前飞行,或者房间的光线发生了显著变化)。第二个挑战可能来自相机的移动。拥有固定的背景和相机位置使无人机检测任务对于初学者的OpenCV教程来说足够简单,而且并不完全不现实。视频检测/识别通常用于完全受控的环境,如工厂的一部分... OpenCV能够处理更复杂的场景 - 可以阅读有关背景减法技术和光流的更多信息...
知道可以使用QueryFrame获取单个帧图像(Mat实例)并前进到下一帧,知道QueryFrame返回null如果它不能再进一步。让利用这些知识构建一个循环遍历帧的方法:
private static void VideoProcessingLoop(VideoCapture capture, Mat backgroundFrame)
{
var stopwatch = new Stopwatch();
int frameNumber = 1;
while (true)
{
var 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;
}
}
}
在每次循环迭代中,从视频文件中捕获一帧。然后将其传递给ProcessFrame方法,该方法执行图像差异、噪声去除、轮廓检测和绘制(将在下一篇文章中详细讨论)... ProcessFrame调用被System.Diagnostics.Stopwatch使用所包围 - 这样,可以测量视频处理性能。笔记本电脑处理每帧只需要大约1.5ms - 告诉过OpenCV很快!
如果QueryFrame返回null,则程序通过调用VideoCapture实例上的SetCaptureProperty方法返回到第一帧(视频将再次处理)。WriteFrameInfo在帧的左上角放置文本,包含有关其编号和处理时间的信息。ShowWindowsWithImageProcessingStages确保可以看到当前(原始)帧、背景帧、中间帧和最终帧在单独的窗口中... 这两种方法将在下一篇文章中展示。
while循环将无限旋转,除非通过在显示帧的任何窗口中按下Escape键来停止程序执行(不是控制台窗口!)。如果将0作为WaitKey参数传递,那么程序将等待直到按下某个键。这让可以想看每帧多久就看多久。如果向WaitKey传递其他数字,那么程序将等待直到按下键或延迟到期。可能会使用它来自动以指定的帧速率播放视频:
int fps = (int)capture.GetCaptureProperty(CapProp.Fps);
int key = CvInvoke.WaitKey(1000 / fps);